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 ("sql", "open") => {
1594 let path = expect_str(args.first())?.to_string();
1595 if path.starts_with("postgres://") || path.starts_with("postgresql://") {
1596 match postgres::Client::connect(&path, postgres::NoTls) {
1598 Ok(client) => {
1599 let handle = next_sql_handle();
1600 sql_registry().lock().unwrap().insert(handle, SqlConn::Postgres(client));
1601 Ok(ok(Value::Int(handle as i64)))
1602 }
1603 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.open"))),
1604 }
1605 } else {
1606 if path != ":memory:" && !self.policy.allow_fs_write.is_empty() {
1609 let p = std::path::Path::new(&path);
1610 if !self.policy.allow_fs_write.iter().any(|a| p.starts_with(a)) {
1611 return Ok(err(sql_error(
1612 format!("sql.open: `{path}` outside --allow-fs-write"),
1613 None, None,
1614 )));
1615 }
1616 }
1617 match rusqlite::Connection::open(&path) {
1618 Ok(conn) => {
1619 let handle = next_sql_handle();
1620 sql_registry().lock().unwrap().insert(handle, SqlConn::Sqlite(conn));
1621 Ok(ok(Value::Int(handle as i64)))
1622 }
1623 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.open"))),
1624 }
1625 }
1626 }
1627 ("sql", "close") => {
1628 let h = expect_sql_handle(args.first())?;
1629 sql_registry().lock().unwrap().remove(h);
1630 Ok(Value::Unit)
1631 }
1632 ("sql", "exec") => {
1633 let h = expect_sql_handle(args.first())?;
1634 let stmt = expect_str(args.get(1))?.to_string();
1635 let params = expect_sql_params(args.get(2))?;
1636 let arc = sql_registry().lock().unwrap()
1637 .touch_get(h)
1638 .ok_or_else(|| "sql.exec: closed or unknown Db handle".to_string())?;
1639 let mut conn = arc.lock().unwrap();
1640 match &mut *conn {
1641 SqlConn::Sqlite(c) => {
1642 let bound = sqlite_params(¶ms);
1643 let bind: Vec<&dyn rusqlite::ToSql> =
1644 bound.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
1645 match c.execute(&stmt, rusqlite::params_from_iter(bind.iter())) {
1646 Ok(n) => Ok(ok(Value::Int(n as i64))),
1647 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.exec"))),
1648 }
1649 }
1650 SqlConn::Postgres(c) => {
1651 let pg = pg_param_refs(¶ms);
1652 let refs: Vec<&(dyn postgres::types::ToSql + Sync)> =
1653 pg.iter().map(|b| b.as_ref()).collect();
1654 match c.execute(stmt.as_str(), &refs) {
1655 Ok(n) => Ok(ok(Value::Int(n as i64))),
1656 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.exec"))),
1657 }
1658 }
1659 }
1660 }
1661 ("sql", "query") => {
1662 let h = expect_sql_handle(args.first())?;
1663 let stmt_str = expect_str(args.get(1))?.to_string();
1664 let params = expect_sql_params(args.get(2))?;
1665 let arc = sql_registry().lock().unwrap()
1666 .touch_get(h)
1667 .ok_or_else(|| "sql.query: closed or unknown Db handle".to_string())?;
1668 let mut conn = arc.lock().unwrap();
1669 Ok(match &mut *conn {
1670 SqlConn::Sqlite(c) => sql_run_query_sqlite(c, &stmt_str, ¶ms),
1671 SqlConn::Postgres(c) => sql_run_query_pg(c, &stmt_str, ¶ms),
1672 })
1673 }
1674 ("sql", "query_iter") => {
1680 let h = expect_sql_handle(args.first())?;
1681 let stmt_str = expect_str(args.get(1))?.to_string();
1682 let params = expect_sql_params(args.get(2))?;
1683 let arc = sql_registry().lock().unwrap()
1684 .touch_get(h)
1685 .ok_or_else(|| "sql.query_iter: closed or unknown Db handle".to_string())?;
1686
1687 let (sender, receiver) = std::sync::mpsc::sync_channel::<Result<Value, String>>(
1691 CURSOR_CHANNEL_CAPACITY,
1692 );
1693 let cursor_h = next_cursor_handle();
1694 cursor_registry().lock().unwrap().insert(cursor_h, receiver);
1695
1696 let arc_for_thread = Arc::clone(&arc);
1697 let is_sqlite = matches!(*arc.lock().unwrap(), SqlConn::Sqlite(_));
1703 std::thread::spawn(move || {
1704 if is_sqlite {
1705 sqlite_cursor_producer(arc_for_thread, stmt_str, params, sender);
1706 } else {
1707 pg_cursor_producer(arc_for_thread, stmt_str, params, sender);
1708 }
1709 });
1710
1711 Ok(ok(Value::Variant {
1712 name: "__IterCursor".into(),
1713 args: vec![Value::Int(cursor_h as i64)],
1714 }))
1715 }
1716 ("sql", "cursor_next") => {
1722 let h = match args.first() {
1723 Some(Value::Int(n)) if *n >= 0 => *n as u64,
1724 _ => return Err("sql.cursor_next: expected cursor handle (Int)".into()),
1725 };
1726 let rx_arc = match cursor_registry().lock().unwrap().touch_get(h) {
1727 Some(a) => a,
1728 None => return Ok(Value::Variant { name: "None".into(), args: vec![] }),
1729 };
1730 let recv_result = {
1735 let rx = match rx_arc.lock() {
1736 Ok(g) => g,
1737 Err(p) => p.into_inner(),
1738 };
1739 rx.recv()
1740 };
1741 match recv_result {
1742 Ok(Ok(row)) => Ok(Value::Variant {
1743 name: "Some".into(),
1744 args: vec![row],
1745 }),
1746 Ok(Err(_)) | Err(_) => {
1747 cursor_registry().lock().unwrap().remove(h);
1751 Ok(Value::Variant { name: "None".into(), args: vec![] })
1752 }
1753 }
1754 }
1755 ("sql", "begin") => {
1760 let h = expect_sql_handle(args.first())?;
1761 let arc = sql_registry().lock().unwrap()
1762 .touch_get(h)
1763 .ok_or_else(|| "sql.begin: closed or unknown Db handle".to_string())?;
1764 let mut conn = arc.lock().unwrap();
1765 match &mut *conn {
1766 SqlConn::Sqlite(c) => match c.execute_batch("BEGIN") {
1767 Ok(()) => Ok(ok(Value::Int(h as i64))),
1768 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.begin"))),
1769 },
1770 SqlConn::Postgres(c) => match c.batch_execute("BEGIN") {
1771 Ok(()) => Ok(ok(Value::Int(h as i64))),
1772 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.begin"))),
1773 },
1774 }
1775 }
1776 ("sql", "commit") => {
1777 let h = expect_sql_handle(args.first())?;
1778 let arc = sql_registry().lock().unwrap()
1779 .touch_get(h)
1780 .ok_or_else(|| "sql.commit: closed or unknown SqlTx handle".to_string())?;
1781 let mut conn = arc.lock().unwrap();
1782 match &mut *conn {
1783 SqlConn::Sqlite(c) => match c.execute_batch("COMMIT") {
1784 Ok(()) => Ok(ok(Value::Unit)),
1785 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.commit"))),
1786 },
1787 SqlConn::Postgres(c) => match c.batch_execute("COMMIT") {
1788 Ok(()) => Ok(ok(Value::Unit)),
1789 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.commit"))),
1790 },
1791 }
1792 }
1793 ("sql", "rollback") => {
1794 let h = expect_sql_handle(args.first())?;
1795 let arc = sql_registry().lock().unwrap()
1796 .touch_get(h)
1797 .ok_or_else(|| "sql.rollback: closed or unknown SqlTx handle".to_string())?;
1798 let mut conn = arc.lock().unwrap();
1799 match &mut *conn {
1800 SqlConn::Sqlite(c) => match c.execute_batch("ROLLBACK") {
1801 Ok(()) => Ok(ok(Value::Unit)),
1802 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.rollback"))),
1803 },
1804 SqlConn::Postgres(c) => match c.batch_execute("ROLLBACK") {
1805 Ok(()) => Ok(ok(Value::Unit)),
1806 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.rollback"))),
1807 },
1808 }
1809 }
1810 ("sql", "exec_tx") => {
1811 let h = expect_sql_handle(args.first())?;
1812 let stmt = expect_str(args.get(1))?.to_string();
1813 let params = expect_sql_params(args.get(2))?;
1814 let arc = sql_registry().lock().unwrap()
1815 .touch_get(h)
1816 .ok_or_else(|| "sql.exec_tx: closed or unknown SqlTx handle".to_string())?;
1817 let mut conn = arc.lock().unwrap();
1818 match &mut *conn {
1819 SqlConn::Sqlite(c) => {
1820 let bound = sqlite_params(¶ms);
1821 let bind: Vec<&dyn rusqlite::ToSql> =
1822 bound.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
1823 match c.execute(&stmt, rusqlite::params_from_iter(bind.iter())) {
1824 Ok(n) => Ok(ok(Value::Int(n as i64))),
1825 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.exec_tx"))),
1826 }
1827 }
1828 SqlConn::Postgres(c) => {
1829 let pg = pg_param_refs(¶ms);
1830 let refs: Vec<&(dyn postgres::types::ToSql + Sync)> =
1831 pg.iter().map(|b| b.as_ref()).collect();
1832 match c.execute(stmt.as_str(), &refs) {
1833 Ok(n) => Ok(ok(Value::Int(n as i64))),
1834 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.exec_tx"))),
1835 }
1836 }
1837 }
1838 }
1839 ("sql", "query_tx") => {
1840 let h = expect_sql_handle(args.first())?;
1841 let stmt_str = expect_str(args.get(1))?.to_string();
1842 let params = expect_sql_params(args.get(2))?;
1843 let arc = sql_registry().lock().unwrap()
1844 .touch_get(h)
1845 .ok_or_else(|| "sql.query_tx: closed or unknown SqlTx handle".to_string())?;
1846 let mut conn = arc.lock().unwrap();
1847 Ok(match &mut *conn {
1848 SqlConn::Sqlite(c) => sql_run_query_sqlite(c, &stmt_str, ¶ms),
1849 SqlConn::Postgres(c) => sql_run_query_pg(c, &stmt_str, ¶ms),
1850 })
1851 }
1852 ("sql", "get_str") => Ok(sql_get_col(&args, |v| match v {
1853 Value::Str(s) => Some(Value::Str(s.clone())),
1854 Value::Int(n) => Some(Value::Str(n.to_string().into())),
1855 _ => None,
1856 })?),
1857 ("sql", "get_int") => Ok(sql_get_col(&args, |v| match v {
1858 Value::Int(n) => Some(Value::Int(*n)),
1859 Value::Float(f) => Some(Value::Int(*f as i64)),
1860 _ => None,
1861 })?),
1862 ("sql", "get_float") => Ok(sql_get_col(&args, |v| match v {
1863 Value::Float(f) => Some(Value::Float(*f)),
1864 Value::Int(n) => Some(Value::Float(*n as f64)),
1865 _ => None,
1866 })?),
1867 ("sql", "get_bool") => Ok(sql_get_col(&args, |v| match v {
1868 Value::Bool(b) => Some(Value::Bool(*b)),
1869 Value::Int(n) => Some(Value::Bool(*n != 0)),
1870 _ => None,
1871 })?),
1872
1873 ("redis", "connect") => {
1882 let url = expect_str(args.first())?.to_string();
1883 self.ensure_host_allowed(&url)?;
1884 match redis::Client::open(url.as_str()) {
1885 Ok(client) => match client.get_connection() {
1886 Ok(conn) => {
1887 let handle = next_redis_handle();
1888 redis_registry().lock().unwrap().insert(handle, RedisEntry { url, conn });
1889 Ok(ok(Value::Int(handle as i64)))
1890 }
1891 Err(e) => Ok(err(Value::Str(format!("redis.connect: {e}").into()))),
1892 },
1893 Err(e) => Ok(err(Value::Str(format!("redis.connect: {e}").into()))),
1894 }
1895 }
1896 ("redis", "close") => {
1897 let h = expect_redis_handle(args.first())?;
1898 redis_registry().lock().unwrap().remove(h);
1899 Ok(Value::Unit)
1900 }
1901 ("redis", "get") => {
1902 let h = expect_redis_handle(args.first())?;
1903 let key = expect_str(args.get(1))?.to_string();
1904 let mut reg = redis_registry().lock().unwrap();
1905 let entry = reg.touch_get_mut(h)
1906 .ok_or_else(|| "redis.get: closed or unknown ConnRedis handle".to_string())?;
1907 use redis::Commands;
1908 match entry.conn.get::<_, Option<String>>(&key) {
1909 Ok(Some(v)) => Ok(some(Value::Str(v.into()))),
1910 Ok(None) => Ok(none()),
1911 Err(e) => Err(format!("redis.get: {e}")),
1912 }
1913 }
1914 ("redis", "set") => {
1915 let h = expect_redis_handle(args.first())?;
1916 let key = expect_str(args.get(1))?.to_string();
1917 let val = expect_str(args.get(2))?.to_string();
1918 let mut reg = redis_registry().lock().unwrap();
1919 let entry = reg.touch_get_mut(h)
1920 .ok_or_else(|| "redis.set: closed or unknown ConnRedis handle".to_string())?;
1921 use redis::Commands;
1922 entry.conn.set::<_, _, ()>(&key, &val)
1923 .map_err(|e| format!("redis.set: {e}"))?;
1924 Ok(Value::Unit)
1925 }
1926 ("redis", "set_ex") => {
1927 let h = expect_redis_handle(args.first())?;
1928 let key = expect_str(args.get(1))?.to_string();
1929 let val = expect_str(args.get(2))?.to_string();
1930 let ttl = expect_int(args.get(3))?;
1931 let mut reg = redis_registry().lock().unwrap();
1932 let entry = reg.touch_get_mut(h)
1933 .ok_or_else(|| "redis.set_ex: closed or unknown ConnRedis handle".to_string())?;
1934 use redis::Commands;
1935 entry.conn.set_ex::<_, _, ()>(&key, &val, ttl as u64)
1936 .map_err(|e| format!("redis.set_ex: {e}"))?;
1937 Ok(Value::Unit)
1938 }
1939 ("redis", "del") => {
1940 let h = expect_redis_handle(args.first())?;
1941 let key = expect_str(args.get(1))?.to_string();
1942 let mut reg = redis_registry().lock().unwrap();
1943 let entry = reg.touch_get_mut(h)
1944 .ok_or_else(|| "redis.del: closed or unknown ConnRedis handle".to_string())?;
1945 use redis::Commands;
1946 entry.conn.del::<_, ()>(&key)
1947 .map_err(|e| format!("redis.del: {e}"))?;
1948 Ok(Value::Unit)
1949 }
1950 ("redis", "exists") => {
1951 let h = expect_redis_handle(args.first())?;
1952 let key = expect_str(args.get(1))?.to_string();
1953 let mut reg = redis_registry().lock().unwrap();
1954 let entry = reg.touch_get_mut(h)
1955 .ok_or_else(|| "redis.exists: closed or unknown ConnRedis handle".to_string())?;
1956 use redis::Commands;
1957 let present: bool = entry.conn.exists(&key)
1958 .map_err(|e| format!("redis.exists: {e}"))?;
1959 Ok(Value::Bool(present))
1960 }
1961 ("redis", "expire") => {
1962 let h = expect_redis_handle(args.first())?;
1963 let key = expect_str(args.get(1))?.to_string();
1964 let ttl = expect_int(args.get(2))?;
1965 let mut reg = redis_registry().lock().unwrap();
1966 let entry = reg.touch_get_mut(h)
1967 .ok_or_else(|| "redis.expire: closed or unknown ConnRedis handle".to_string())?;
1968 use redis::Commands;
1969 entry.conn.expire::<_, ()>(&key, ttl)
1970 .map_err(|e| format!("redis.expire: {e}"))?;
1971 Ok(Value::Unit)
1972 }
1973 ("redis", "publish") => {
1974 let h = expect_redis_handle(args.first())?;
1975 let channel = expect_str(args.get(1))?.to_string();
1976 let msg = expect_str(args.get(2))?.to_string();
1977 let mut reg = redis_registry().lock().unwrap();
1978 let entry = reg.touch_get_mut(h)
1979 .ok_or_else(|| "redis.publish: closed or unknown ConnRedis handle".to_string())?;
1980 use redis::Commands;
1981 let n: i64 = entry.conn.publish(&channel, &msg)
1982 .map_err(|e| format!("redis.publish: {e}"))?;
1983 Ok(Value::Int(n))
1984 }
1985 ("redis", "subscribe") => {
1990 let h = expect_redis_handle(args.first())?;
1991 let channel = expect_str(args.get(1))?.to_string();
1992 let closure = match args.into_iter().nth(2) {
1993 Some(c @ Value::Closure { .. }) => c,
1994 _ => return Err("redis.subscribe: handler must be a Closure".into()),
1995 };
1996 let program = self.program.clone()
1997 .ok_or("redis.subscribe: no program; call DefaultHandler::with_program")?;
1998 let policy = self.policy.clone();
1999 let url = redis_registry().lock().unwrap()
2000 .get_url(h)
2001 .ok_or("redis.subscribe: closed or unknown ConnRedis handle")?;
2002 let client = redis::Client::open(url.as_str())
2003 .map_err(|e| format!("redis.subscribe: {e}"))?;
2004 let mut conn = client.get_connection()
2005 .map_err(|e| format!("redis.subscribe: {e}"))?;
2006 let mut pubsub = conn.as_pubsub();
2007 pubsub.subscribe(&channel)
2008 .map_err(|e| format!("redis.subscribe: {e}"))?;
2009 loop {
2010 let msg = pubsub.get_message()
2011 .map_err(|e| format!("redis.subscribe: {e}"))?;
2012 let ch: String = msg.get_channel_name().to_string();
2013 let payload: String = msg.get_payload()
2014 .map_err(|e| format!("redis.subscribe: payload: {e}"))?;
2015 let handler = DefaultHandler::new(policy.clone())
2016 .with_program(Arc::clone(&program));
2017 let mut vm = Vm::with_handler(&program, Box::new(handler));
2018 vm.invoke_closure_value(closure.clone(), vec![
2019 Value::Str(ch.into()),
2020 Value::Str(payload.into()),
2021 ]).map_err(|e| format!("redis.subscribe: handler: {e:?}"))?;
2022 }
2023 }
2024 ("redis", "psubscribe") => {
2025 let h = expect_redis_handle(args.first())?;
2026 let pattern = expect_str(args.get(1))?.to_string();
2027 let closure = match args.into_iter().nth(2) {
2028 Some(c @ Value::Closure { .. }) => c,
2029 _ => return Err("redis.psubscribe: handler must be a Closure".into()),
2030 };
2031 let program = self.program.clone()
2032 .ok_or("redis.psubscribe: no program; call DefaultHandler::with_program")?;
2033 let policy = self.policy.clone();
2034 let url = redis_registry().lock().unwrap()
2035 .get_url(h)
2036 .ok_or("redis.psubscribe: closed or unknown ConnRedis handle")?;
2037 let client = redis::Client::open(url.as_str())
2038 .map_err(|e| format!("redis.psubscribe: {e}"))?;
2039 let mut conn = client.get_connection()
2040 .map_err(|e| format!("redis.psubscribe: {e}"))?;
2041 let mut pubsub = conn.as_pubsub();
2042 pubsub.psubscribe(&pattern)
2043 .map_err(|e| format!("redis.psubscribe: {e}"))?;
2044 loop {
2045 let msg = pubsub.get_message()
2046 .map_err(|e| format!("redis.psubscribe: {e}"))?;
2047 let pat: String = msg.get_pattern()
2048 .ok()
2049 .and_then(|v: Option<String>| v)
2050 .unwrap_or_else(|| pattern.clone());
2051 let ch: String = msg.get_channel_name().to_string();
2052 let payload: String = msg.get_payload()
2053 .map_err(|e| format!("redis.psubscribe: payload: {e}"))?;
2054 let handler = DefaultHandler::new(policy.clone())
2055 .with_program(Arc::clone(&program));
2056 let mut vm = Vm::with_handler(&program, Box::new(handler));
2057 vm.invoke_closure_value(closure.clone(), vec![
2058 Value::Str(pat.into()),
2059 Value::Str(ch.into()),
2060 Value::Str(payload.into()),
2061 ]).map_err(|e| format!("redis.psubscribe: handler: {e:?}"))?;
2062 }
2063 }
2064 ("redis", "lpush") => {
2065 let h = expect_redis_handle(args.first())?;
2066 let key = expect_str(args.get(1))?.to_string();
2067 let val = expect_str(args.get(2))?.to_string();
2068 let mut reg = redis_registry().lock().unwrap();
2069 let entry = reg.touch_get_mut(h)
2070 .ok_or_else(|| "redis.lpush: closed or unknown ConnRedis handle".to_string())?;
2071 use redis::Commands;
2072 let n: i64 = entry.conn.lpush(&key, &val)
2073 .map_err(|e| format!("redis.lpush: {e}"))?;
2074 Ok(Value::Int(n))
2075 }
2076 ("redis", "rpush") => {
2077 let h = expect_redis_handle(args.first())?;
2078 let key = expect_str(args.get(1))?.to_string();
2079 let val = expect_str(args.get(2))?.to_string();
2080 let mut reg = redis_registry().lock().unwrap();
2081 let entry = reg.touch_get_mut(h)
2082 .ok_or_else(|| "redis.rpush: closed or unknown ConnRedis handle".to_string())?;
2083 use redis::Commands;
2084 let n: i64 = entry.conn.rpush(&key, &val)
2085 .map_err(|e| format!("redis.rpush: {e}"))?;
2086 Ok(Value::Int(n))
2087 }
2088 ("redis", "brpop") => {
2089 let h = expect_redis_handle(args.first())?;
2092 let key = expect_str(args.get(1))?.to_string();
2093 let timeout = expect_int(args.get(2))?;
2094 let mut reg = redis_registry().lock().unwrap();
2095 let entry = reg.touch_get_mut(h)
2096 .ok_or_else(|| "redis.brpop: closed or unknown ConnRedis handle".to_string())?;
2097 use redis::Commands;
2098 let result: Option<(String, String)> = entry.conn
2101 .brpop(&key, timeout as f64)
2102 .map_err(|e| format!("redis.brpop: {e}"))?;
2103 match result {
2104 Some((_, v)) => Ok(some(Value::Str(v.into()))),
2105 None => Ok(none()),
2106 }
2107 }
2108 ("redis", "llen") => {
2109 let h = expect_redis_handle(args.first())?;
2110 let key = expect_str(args.get(1))?.to_string();
2111 let mut reg = redis_registry().lock().unwrap();
2112 let entry = reg.touch_get_mut(h)
2113 .ok_or_else(|| "redis.llen: closed or unknown ConnRedis handle".to_string())?;
2114 use redis::Commands;
2115 let n: i64 = entry.conn.llen(&key)
2116 .map_err(|e| format!("redis.llen: {e}"))?;
2117 Ok(Value::Int(n))
2118 }
2119 ("redis", "hset") => {
2120 let h = expect_redis_handle(args.first())?;
2121 let key = expect_str(args.get(1))?.to_string();
2122 let field = expect_str(args.get(2))?.to_string();
2123 let val = expect_str(args.get(3))?.to_string();
2124 let mut reg = redis_registry().lock().unwrap();
2125 let entry = reg.touch_get_mut(h)
2126 .ok_or_else(|| "redis.hset: closed or unknown ConnRedis handle".to_string())?;
2127 use redis::Commands;
2128 entry.conn.hset::<_, _, _, ()>(&key, &field, &val)
2129 .map_err(|e| format!("redis.hset: {e}"))?;
2130 Ok(Value::Unit)
2131 }
2132 ("redis", "hget") => {
2133 let h = expect_redis_handle(args.first())?;
2134 let key = expect_str(args.get(1))?.to_string();
2135 let field = expect_str(args.get(2))?.to_string();
2136 let mut reg = redis_registry().lock().unwrap();
2137 let entry = reg.touch_get_mut(h)
2138 .ok_or_else(|| "redis.hget: closed or unknown ConnRedis handle".to_string())?;
2139 use redis::Commands;
2140 match entry.conn.hget::<_, _, Option<String>>(&key, &field) {
2141 Ok(Some(v)) => Ok(some(Value::Str(v.into()))),
2142 Ok(None) => Ok(none()),
2143 Err(e) => Err(format!("redis.hget: {e}")),
2144 }
2145 }
2146 ("redis", "hdel") => {
2147 let h = expect_redis_handle(args.first())?;
2148 let key = expect_str(args.get(1))?.to_string();
2149 let field = expect_str(args.get(2))?.to_string();
2150 let mut reg = redis_registry().lock().unwrap();
2151 let entry = reg.touch_get_mut(h)
2152 .ok_or_else(|| "redis.hdel: closed or unknown ConnRedis handle".to_string())?;
2153 use redis::Commands;
2154 entry.conn.hdel::<_, _, ()>(&key, &field)
2155 .map_err(|e| format!("redis.hdel: {e}"))?;
2156 Ok(Value::Unit)
2157 }
2158 ("redis", "hgetall") => {
2159 let h = expect_redis_handle(args.first())?;
2160 let key = expect_str(args.get(1))?.to_string();
2161 let mut reg = redis_registry().lock().unwrap();
2162 let entry = reg.touch_get_mut(h)
2163 .ok_or_else(|| "redis.hgetall: closed or unknown ConnRedis handle".to_string())?;
2164 use redis::Commands;
2165 let map: std::collections::HashMap<String, String> = entry.conn
2166 .hgetall(&key)
2167 .map_err(|e| format!("redis.hgetall: {e}"))?;
2168 let pairs: Vec<Value> = map.into_iter()
2169 .map(|(k, v)| Value::Tuple(vec![Value::Str(k.into()), Value::Str(v.into())]))
2170 .collect();
2171 Ok(Value::List(pairs.into()))
2172 }
2173
2174 other => Err(format!("unsupported effect {}.{}", other.0, other.1)),
2178 }
2179 }
2180
2181 fn spawn_for_worker(&self) -> Option<Box<dyn lex_bytecode::vm::EffectHandler + Send>> {
2204 let mut fresh = DefaultHandler::new(self.policy.clone());
2205 fresh.budget_remaining = std::sync::Arc::clone(&self.budget_remaining);
2208 fresh.budget_ceiling = self.budget_ceiling;
2209 fresh.read_root = self.read_root.clone();
2210 fresh.program = self.program.clone();
2211 fresh.chat_registry = self.chat_registry.clone();
2212 fresh.streams = std::sync::Arc::clone(&self.streams);
2217 fresh.next_stream_id = std::sync::Arc::clone(&self.next_stream_id);
2218 fresh.program_args = self.program_args.clone();
2219 Some(Box::new(fresh))
2220 }
2221}
2222
2223pub struct TlsConfig {
2233 pub cert: Vec<u8>,
2234 pub key: Vec<u8>,
2235}
2236
2237fn serve_http(
2238 port: u16,
2239 handler_name: String,
2240 program: Arc<Program>,
2241 policy: Policy,
2242 tls: Option<TlsConfig>,
2243 opts: ServeOpts,
2244) -> Result<Value, String> {
2245 match tls {
2246 None => serve_http_plain(port, handler_name, program, policy, opts),
2247 Some(cfg) => serve_http_tls_legacy(port, handler_name, program, policy, cfg),
2248 }
2249}
2250
2251fn serve_http_plain(
2261 port: u16,
2262 handler_name: String,
2263 program: Arc<Program>,
2264 policy: Policy,
2265 opts: ServeOpts,
2266) -> Result<Value, String> {
2267 use http_body_util::BodyExt as _;
2268 use hyper::server::conn::http1;
2269 use hyper::service::service_fn;
2270 use hyper_util::rt::{TokioExecutor, TokioIo};
2271 use hyper_util::server::conn::auto;
2272 use tokio::net::TcpListener as TokioTcpListener;
2273
2274 let inline_vm = opts.inline_vm;
2275 let http2 = opts.http2;
2276 let host = opts.host.clone();
2277 let rt = tokio::runtime::Builder::new_multi_thread()
2278 .enable_all()
2279 .build()
2280 .map_err(|e| format!("net.serve: tokio runtime: {e}"))?;
2281 rt.block_on(async move {
2282 let listener = TokioTcpListener::bind((host.as_str(), port))
2283 .await
2284 .map_err(|e| format!("net.serve bind {host}:{port}: {e}"))?;
2285 eprintln!(
2286 "net.serve: listening on http://{host}:{port}{}{}",
2287 if inline_vm { " (inline-vm)" } else { "" },
2288 if http2 { " (http1+http2)" } else { "" }
2289 );
2290 loop {
2291 let (stream, _) = listener
2292 .accept()
2293 .await
2294 .map_err(|e| format!("net.serve accept: {e}"))?;
2295 let io = TokioIo::new(stream);
2296 let program = Arc::clone(&program);
2297 let policy = policy.clone();
2298 let handler_name = handler_name.clone();
2299 tokio::spawn(async move {
2300 let program2 = Arc::clone(&program);
2301 let policy2 = policy.clone();
2302 let handler_name2 = handler_name.clone();
2303 let svc = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
2304 let program = Arc::clone(&program2);
2305 let policy = policy2.clone();
2306 let handler_name = handler_name2.clone();
2307 async move {
2308 let (parts, body) = req.into_parts();
2309 let body_bytes = body
2310 .collect()
2311 .await
2312 .map(|c| c.to_bytes())
2313 .unwrap_or_default();
2314 let result = if inline_vm {
2315 let lex_req = build_request_value_parts(&parts, &body_bytes);
2319 let handler = DefaultHandler::new(policy)
2320 .with_program(Arc::clone(&program));
2321 let mut vm = Vm::with_handler(&program, Box::new(handler));
2322 let r = vm.call(&handler_name, vec![lex_req]);
2323 Ok(r.map(|v| unpack_response(&mut vm, &v)))
2326 } else {
2327 tokio::task::spawn_blocking(move || {
2328 let lex_req = build_request_value_parts(&parts, &body_bytes);
2329 let handler = DefaultHandler::new(policy)
2330 .with_program(Arc::clone(&program));
2331 let mut vm = Vm::with_handler(&program, Box::new(handler));
2332 let r = vm.call(&handler_name, vec![lex_req]);
2333 r.map(|v| unpack_response(&mut vm, &v))
2334 })
2335 .await
2336 };
2337 Ok::<_, std::convert::Infallible>(match result {
2338 Ok(Ok(unpacked)) => build_hyper_response(unpacked),
2339 Ok(Err(e)) => error_response(500, &format!("internal error: {e}")),
2340 Err(e) => error_response(500, &format!("task panicked: {e}")),
2341 })
2342 }
2343 });
2344 let result = if http2 {
2345 auto::Builder::new(TokioExecutor::new())
2346 .serve_connection(io, svc)
2347 .await
2348 .map_err(|e| e.to_string())
2349 } else {
2350 http1::Builder::new()
2351 .serve_connection(io, svc)
2352 .await
2353 .map_err(|e| e.to_string())
2354 };
2355 if let Err(e) = result {
2356 eprintln!("net.serve: connection error: {e}");
2357 }
2358 });
2359 }
2360 })
2361}
2362
2363fn serve_http_tls_legacy(
2365 port: u16,
2366 handler_name: String,
2367 program: Arc<Program>,
2368 policy: Policy,
2369 cfg: TlsConfig,
2370) -> Result<Value, String> {
2371 let ssl = tiny_http::SslConfig {
2372 certificate: cfg.cert,
2373 private_key: cfg.key,
2374 };
2375 let server = tiny_http::Server::https(("0.0.0.0", port), ssl)
2376 .map_err(|e| format!("net.serve_tls bind {port}: {e}"))?;
2377 eprintln!("net.serve: listening on https://0.0.0.0:{port}");
2378 for req in server.incoming_requests() {
2379 let program = Arc::clone(&program);
2380 let policy = policy.clone();
2381 let handler_name = handler_name.clone();
2382 std::thread::spawn(move || handle_request_tls(req, program, policy, handler_name));
2383 }
2384 Ok(Value::Unit)
2385}
2386
2387fn handle_request_tls(
2388 mut req: tiny_http::Request,
2389 program: Arc<Program>,
2390 policy: Policy,
2391 handler_name: String,
2392) {
2393 let lex_req = build_request_value_tiny(&mut req);
2394 let handler = DefaultHandler::new(policy).with_program(Arc::clone(&program));
2395 let mut vm = Vm::with_handler(&program, Box::new(handler));
2396 match vm.call(&handler_name, vec![lex_req]) {
2397 Ok(resp) => {
2398 let (status, body, headers) = unpack_response(&mut vm, &resp);
2404 respond_with_body_tls(req, status, body, headers);
2405 }
2406 Err(e) => {
2407 let response = tiny_http::Response::from_string(format!("internal error: {e}"))
2408 .with_status_code(500);
2409 let _ = req.respond(response);
2410 }
2411 }
2412}
2413
2414fn serve_http_fn(
2419 port: u16,
2420 closure: Value,
2421 program: Arc<Program>,
2422 policy: Policy,
2423 opts: ServeOpts,
2424) -> Result<Value, String> {
2425 use http_body_util::BodyExt as _;
2426 use hyper::server::conn::http1;
2427 use hyper::service::service_fn;
2428 use hyper_util::rt::{TokioExecutor, TokioIo};
2429 use hyper_util::server::conn::auto;
2430 use tokio::net::TcpListener as TokioTcpListener;
2431
2432 let inline_vm = opts.inline_vm;
2433 let http2 = opts.http2;
2434 let host = opts.host.clone();
2435 let rt = tokio::runtime::Builder::new_multi_thread()
2436 .enable_all()
2437 .build()
2438 .map_err(|e| format!("net.serve_fn: tokio runtime: {e}"))?;
2439 rt.block_on(async move {
2440 let listener = TokioTcpListener::bind((host.as_str(), port))
2441 .await
2442 .map_err(|e| format!("net.serve_fn bind {host}:{port}: {e}"))?;
2443 eprintln!(
2444 "net.serve_fn: listening on http://{host}:{port}{}{}",
2445 if inline_vm { " (inline-vm)" } else { "" },
2446 if http2 { " (http1+http2)" } else { "" }
2447 );
2448 loop {
2449 let (stream, _) = listener
2450 .accept()
2451 .await
2452 .map_err(|e| format!("net.serve_fn accept: {e}"))?;
2453 let io = TokioIo::new(stream);
2454 let program = Arc::clone(&program);
2455 let policy = policy.clone();
2456 let closure = closure.clone();
2457 tokio::spawn(async move {
2458 let program2 = Arc::clone(&program);
2459 let policy2 = policy.clone();
2460 let closure2 = closure.clone();
2461 let svc = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
2462 let program = Arc::clone(&program2);
2463 let policy = policy2.clone();
2464 let closure = closure2.clone();
2465 async move {
2466 let (parts, body) = req.into_parts();
2467 let body_bytes = body
2468 .collect()
2469 .await
2470 .map(|c| c.to_bytes())
2471 .unwrap_or_default();
2472 let result = if inline_vm {
2473 let lex_req = build_request_value_parts(&parts, &body_bytes);
2474 let handler = DefaultHandler::new(policy)
2475 .with_program(Arc::clone(&program));
2476 let mut vm = Vm::with_handler(&program, Box::new(handler));
2477 let scope = vm.enter_request_scope();
2484 let r = vm.invoke_closure_value(closure, vec![lex_req]);
2485 let r = r.map(|v| unpack_response(&mut vm, &v));
2489 vm.exit_request_scope(scope);
2490 Ok(r)
2491 } else {
2492 tokio::task::spawn_blocking(move || {
2493 let lex_req = build_request_value_parts(&parts, &body_bytes);
2494 let handler = DefaultHandler::new(policy)
2495 .with_program(Arc::clone(&program));
2496 let mut vm = Vm::with_handler(&program, Box::new(handler));
2497 let scope = vm.enter_request_scope();
2498 let r = vm.invoke_closure_value(closure, vec![lex_req]);
2499 let r = r.map(|v| unpack_response(&mut vm, &v));
2500 vm.exit_request_scope(scope);
2501 r
2502 })
2503 .await
2504 };
2505 Ok::<_, std::convert::Infallible>(match result {
2506 Ok(Ok(unpacked)) => build_hyper_response(unpacked),
2507 Ok(Err(e)) => error_response(500, &format!("internal error: {e}")),
2508 Err(e) => error_response(500, &format!("task panicked: {e}")),
2509 })
2510 }
2511 });
2512 let result = if http2 {
2513 auto::Builder::new(TokioExecutor::new())
2514 .serve_connection(io, svc)
2515 .await
2516 .map_err(|e| e.to_string())
2517 } else {
2518 http1::Builder::new()
2519 .serve_connection(io, svc)
2520 .await
2521 .map_err(|e| e.to_string())
2522 };
2523 if let Err(e) = result {
2524 eprintln!("net.serve_fn: connection error: {e}");
2525 }
2526 });
2527 }
2528 })
2529}
2530
2531#[derive(Clone, Debug)]
2535pub(crate) enum RouteSeg {
2536 Literal(String),
2537 Param(String),
2540}
2541
2542fn compile_path_pattern(pat: &str) -> Result<Vec<RouteSeg>, String> {
2546 if pat.is_empty() {
2547 return Err("path pattern must be non-empty (use \"/\" for the root)".into());
2548 }
2549 if !pat.starts_with('/') {
2550 return Err(format!("path pattern must start with '/' (got {pat:?})"));
2551 }
2552 let mut segs = Vec::new();
2553 for raw in pat.split('/') {
2554 if let Some(name) = raw.strip_prefix(':') {
2555 if name.is_empty() {
2556 return Err(format!(
2557 ":-segment in pattern {pat:?} must have a name (e.g. :id)"
2558 ));
2559 }
2560 segs.push(RouteSeg::Param(name.to_string()));
2561 } else {
2562 segs.push(RouteSeg::Literal(raw.to_string()));
2563 }
2564 }
2565 Ok(segs)
2566}
2567
2568fn match_path_pattern(
2574 segs: &[RouteSeg],
2575 path: &str,
2576) -> Option<std::collections::BTreeMap<lex_bytecode::MapKey, Value>> {
2577 let path_segs: Vec<&str> = path.split('/').collect();
2578 if path_segs.len() != segs.len() {
2579 return None;
2580 }
2581 let mut params = std::collections::BTreeMap::new();
2582 for (pat, p) in segs.iter().zip(path_segs.iter()) {
2583 match pat {
2584 RouteSeg::Literal(lit) => {
2585 if lit != p {
2586 return None;
2587 }
2588 }
2589 RouteSeg::Param(name) => {
2590 params.insert(
2591 lex_bytecode::MapKey::Str(name.clone()),
2592 Value::Str((*p).into()),
2593 );
2594 }
2595 }
2596 }
2597 Some(params)
2598}
2599
2600fn decode_routes_arg(
2605 v: Value,
2606) -> Result<Vec<(String, Vec<RouteSeg>, Value)>, String> {
2607 let list = match v {
2608 Value::List(xs) => xs,
2609 _ => return Err("net.serve_routed: routes must be a List".into()),
2610 };
2611 let mut out = Vec::with_capacity(list.len());
2612 for (i, item) in list.into_iter().enumerate() {
2613 let tup = match item {
2614 Value::Tuple(xs) if xs.len() == 3 => xs,
2615 other => return Err(format!(
2616 "net.serve_routed: route #{i} must be a (method, pattern, handler) 3-tuple, got {other:?}"
2617 )),
2618 };
2619 let mut it = tup.into_iter();
2620 let method_raw = match it.next() {
2621 Some(Value::Str(s)) => s.to_string(),
2622 _ => return Err(format!("net.serve_routed: route #{i} method must be Str")),
2623 };
2624 let method = if method_raw == "*" { method_raw } else { method_raw.to_uppercase() };
2626 let pattern = match it.next() {
2627 Some(Value::Str(s)) => s.to_string(),
2628 _ => return Err(format!("net.serve_routed: route #{i} path-pattern must be Str")),
2629 };
2630 let segs = compile_path_pattern(&pattern)
2631 .map_err(|e| format!("net.serve_routed: route #{i} ({pattern:?}): {e}"))?;
2632 let closure = match it.next() {
2633 Some(c @ Value::Closure { .. }) => c,
2634 _ => return Err(format!("net.serve_routed: route #{i} handler must be a closure")),
2635 };
2636 out.push((method, segs, closure));
2637 }
2638 Ok(out)
2639}
2640
2641pub(crate) fn dispatch_route<'a>(
2646 routes: &'a [(String, Vec<RouteSeg>, Value)],
2647 req_method: &str,
2648 req_path: &str,
2649) -> Option<(&'a Value, std::collections::BTreeMap<lex_bytecode::MapKey, Value>)> {
2650 let req_method_upper = req_method.to_ascii_uppercase();
2651 for (m, segs, closure) in routes {
2652 if m != "*" && m != &req_method_upper {
2653 continue;
2654 }
2655 if let Some(params) = match_path_pattern(segs, req_path) {
2656 return Some((closure, params));
2657 }
2658 }
2659 None
2660}
2661
2662pub(crate) fn stamp_path_params(
2666 req: &mut Value,
2667 params: std::collections::BTreeMap<lex_bytecode::MapKey, Value>,
2668) {
2669 if let Value::Record { fields: rec, .. } = req {
2670 rec.insert("path_params".into(), Value::Map(params));
2671 }
2672}
2673
2674fn serve_http_routed(
2680 port: u16,
2681 routes: Vec<(String, Vec<RouteSeg>, Value)>,
2682 fallback: Value,
2683 program: Arc<Program>,
2684 policy: Policy,
2685 opts: ServeOpts,
2686) -> Result<Value, String> {
2687 use http_body_util::BodyExt as _;
2688 use hyper::server::conn::http1;
2689 use hyper::service::service_fn;
2690 use hyper_util::rt::{TokioExecutor, TokioIo};
2691 use hyper_util::server::conn::auto;
2692 use tokio::net::TcpListener as TokioTcpListener;
2693
2694 let inline_vm = opts.inline_vm;
2695 let http2 = opts.http2;
2696 let host = opts.host.clone();
2697 let routes = Arc::new(routes);
2698 let rt = tokio::runtime::Builder::new_multi_thread()
2699 .enable_all()
2700 .build()
2701 .map_err(|e| format!("net.serve_routed: tokio runtime: {e}"))?;
2702 rt.block_on(async move {
2703 let listener = TokioTcpListener::bind((host.as_str(), port))
2704 .await
2705 .map_err(|e| format!("net.serve_routed bind {host}:{port}: {e}"))?;
2706 eprintln!(
2707 "net.serve_routed: listening on http://{host}:{port} ({} routes{}{})",
2708 routes.len(),
2709 if inline_vm { ", inline-vm" } else { "" },
2710 if http2 { ", http1+http2" } else { "" }
2711 );
2712 loop {
2713 let (stream, _) = listener
2714 .accept()
2715 .await
2716 .map_err(|e| format!("net.serve_routed accept: {e}"))?;
2717 let io = TokioIo::new(stream);
2718 let program = Arc::clone(&program);
2719 let policy = policy.clone();
2720 let routes = Arc::clone(&routes);
2721 let fallback = fallback.clone();
2722 tokio::spawn(async move {
2723 let program2 = Arc::clone(&program);
2724 let policy2 = policy.clone();
2725 let routes2 = Arc::clone(&routes);
2726 let fallback2 = fallback.clone();
2727 let svc = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
2728 let program = Arc::clone(&program2);
2729 let policy = policy2.clone();
2730 let routes = Arc::clone(&routes2);
2731 let fallback = fallback2.clone();
2732 async move {
2733 let (parts, body) = req.into_parts();
2734 let body_bytes = body
2735 .collect()
2736 .await
2737 .map(|c| c.to_bytes())
2738 .unwrap_or_default();
2739 let method = parts.method.as_str().to_string();
2740 let path = match parts.uri.path() {
2741 "" => "/".to_string(),
2742 p => p.to_string(),
2743 };
2744 let result = if inline_vm {
2745 let mut lex_req = build_request_value_parts(&parts, &body_bytes);
2746 let (closure, params) = match dispatch_route(&routes, &method, &path) {
2747 Some((c, p)) => (c.clone(), p),
2748 None => (fallback.clone(), std::collections::BTreeMap::new()),
2749 };
2750 stamp_path_params(&mut lex_req, params);
2751 let handler = DefaultHandler::new(policy)
2752 .with_program(Arc::clone(&program));
2753 let mut vm = Vm::with_handler(&program, Box::new(handler));
2754 let r = vm.invoke_closure_value(closure, vec![lex_req]);
2755 Ok(r.map(|v| unpack_response(&mut vm, &v)))
2758 } else {
2759 tokio::task::spawn_blocking(move || {
2760 let mut lex_req = build_request_value_parts(&parts, &body_bytes);
2761 let (closure, params) = match dispatch_route(&routes, &method, &path) {
2762 Some((c, p)) => (c.clone(), p),
2763 None => (fallback.clone(), std::collections::BTreeMap::new()),
2764 };
2765 stamp_path_params(&mut lex_req, params);
2766 let handler = DefaultHandler::new(policy)
2767 .with_program(Arc::clone(&program));
2768 let mut vm = Vm::with_handler(&program, Box::new(handler));
2769 let r = vm.invoke_closure_value(closure, vec![lex_req]);
2770 r.map(|v| unpack_response(&mut vm, &v))
2771 })
2772 .await
2773 };
2774 Ok::<_, std::convert::Infallible>(match result {
2775 Ok(Ok(unpacked)) => build_hyper_response(unpacked),
2776 Ok(Err(e)) => error_response(500, &format!("internal error: {e}")),
2777 Err(e) => error_response(500, &format!("task panicked: {e}")),
2778 })
2779 }
2780 });
2781 let result = if http2 {
2782 auto::Builder::new(TokioExecutor::new())
2783 .serve_connection(io, svc)
2784 .await
2785 .map_err(|e| e.to_string())
2786 } else {
2787 http1::Builder::new()
2788 .serve_connection(io, svc)
2789 .await
2790 .map_err(|e| e.to_string())
2791 };
2792 if let Err(e) = result {
2793 eprintln!("net.serve_routed: connection error: {e}");
2794 }
2795 });
2796 }
2797 })
2798}
2799
2800fn env_inline_vm() -> bool {
2805 match std::env::var("LEX_NET_INLINE_VM") {
2806 Ok(v) => {
2807 let s = v.trim().to_ascii_lowercase();
2808 s == "1" || s == "true"
2809 }
2810 Err(_) => false,
2811 }
2812}
2813
2814#[derive(Debug, Clone)]
2820pub(crate) struct ServeOpts {
2821 pub(crate) http2: bool,
2822 pub(crate) inline_vm: bool,
2823 pub(crate) host: String,
2824}
2825
2826impl ServeOpts {
2827 fn from_env() -> Self {
2831 Self {
2832 http2: env_http2(),
2833 inline_vm: env_inline_vm(),
2834 host: "0.0.0.0".to_string(),
2835 }
2836 }
2837
2838 fn lex_defaults() -> Self {
2843 Self {
2844 http2: false,
2845 inline_vm: false,
2846 host: "0.0.0.0".to_string(),
2847 }
2848 }
2849
2850 fn to_value(&self) -> Value {
2852 let mut rec = indexmap::IndexMap::new();
2853 rec.insert("http2".to_string(), Value::Bool(self.http2));
2854 rec.insert("inline_vm".to_string(), Value::Bool(self.inline_vm));
2855 rec.insert("host".to_string(), Value::Str(self.host.clone().into()));
2856 Value::record_dynamic(rec)
2857 }
2858}
2859
2860fn decode_serve_opts(v: &Value) -> Result<ServeOpts, String> {
2865 let rec = match v {
2866 Value::Record { fields: r, .. } => r,
2867 other => return Err(format!("opts must be a Record, got {other:?}")),
2868 };
2869 let http2 = match rec.get("http2") {
2870 Some(Value::Bool(b)) => *b,
2871 _ => return Err("opts.http2 must be Bool".into()),
2872 };
2873 let inline_vm = match rec.get("inline_vm") {
2874 Some(Value::Bool(b)) => *b,
2875 _ => return Err("opts.inline_vm must be Bool".into()),
2876 };
2877 let host = match rec.get("host") {
2878 Some(Value::Str(s)) => s.to_string(),
2879 _ => return Err("opts.host must be Str".into()),
2880 };
2881 Ok(ServeOpts { http2, inline_vm, host })
2882}
2883
2884fn make_tls_config_value(cert_pem: Vec<u8>, key_pem: Vec<u8>) -> Value {
2894 let mut rec = indexmap::IndexMap::new();
2895 rec.insert("cert".into(), Value::Bytes(cert_pem));
2896 rec.insert("key".into(), Value::Bytes(key_pem));
2897 Value::record_dynamic(rec)
2898}
2899
2900#[cfg(feature = "quic")]
2901fn decode_tls_config(v: &Value) -> Result<crate::quic::QuicTls, String> {
2902 let rec = match v {
2903 Value::Record { fields: r, .. } => r,
2904 other => return Err(format!("TlsConfig: expected Record, got {other:?}")),
2905 };
2906 let cert = match rec.get("cert") {
2907 Some(Value::Bytes(b)) => b.to_vec(),
2908 _ => return Err("TlsConfig.cert: must be Bytes".into()),
2909 };
2910 let key = match rec.get("key") {
2911 Some(Value::Bytes(b)) => b.to_vec(),
2912 _ => return Err("TlsConfig.key: must be Bytes".into()),
2913 };
2914 Ok(crate::quic::QuicTls { cert_pem: cert, key_pem: key })
2915}
2916
2917fn dispatch_tls_from_pem_files(
2918 handler: &DefaultHandler,
2919 args: Vec<Value>,
2920) -> Result<Value, String> {
2921 let cert_path = expect_str(args.first())?.to_string();
2922 let key_path = expect_str(args.get(1))?.to_string();
2923 let cert_resolved = handler.resolve_read_path(&cert_path);
2924 let key_resolved = handler.resolve_read_path(&key_path);
2925 if !handler.policy.allow_fs_read.is_empty() {
2926 let allowed = |p: &std::path::Path| -> bool {
2927 handler.policy.allow_fs_read.iter().any(|a| p.starts_with(a))
2928 };
2929 if !allowed(&cert_resolved) {
2930 return Ok(err(Value::Str(
2931 format!("tls.from_pem_files: cert `{cert_path}` outside --allow-fs-read").into(),
2932 )));
2933 }
2934 if !allowed(&key_resolved) {
2935 return Ok(err(Value::Str(
2936 format!("tls.from_pem_files: key `{key_path}` outside --allow-fs-read").into(),
2937 )));
2938 }
2939 }
2940 let cert = match std::fs::read(&cert_resolved) {
2941 Ok(b) => b,
2942 Err(e) => return Ok(err(Value::Str(format!("read cert {cert_path}: {e}").into()))),
2943 };
2944 let key = match std::fs::read(&key_resolved) {
2945 Ok(b) => b,
2946 Err(e) => return Ok(err(Value::Str(format!("read key {key_path}: {e}").into()))),
2947 };
2948 Ok(ok(make_tls_config_value(cert, key)))
2949}
2950
2951#[cfg(feature = "quic")]
2952fn dispatch_tls_self_signed(args: Vec<Value>) -> Result<Value, String> {
2953 let hostname = expect_str(args.first())?.to_string();
2954 match crate::quic::self_signed_pem(&hostname) {
2955 Ok((cert, key)) => Ok(ok(make_tls_config_value(cert, key))),
2956 Err(e) => Ok(err(Value::Str(format!("tls.self_signed: {e}").into()))),
2957 }
2958}
2959
2960#[cfg(not(feature = "quic"))]
2961fn dispatch_tls_self_signed(_args: Vec<Value>) -> Result<Value, String> {
2962 Ok(err(Value::Str(
2963 "tls.self_signed: lex-runtime was compiled without the `quic` feature (needed for rcgen)".into(),
2964 )))
2965}
2966
2967impl DefaultHandler {
2968 #[cfg(feature = "quic")]
2969 fn dispatch_serve_quic_named(&self, args: Vec<Value>) -> Result<Value, String> {
2970 let port = match args.first() {
2971 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
2972 _ => return Err("net.serve_quic(port, tls, handler): port must be Int 0..=65535".into()),
2973 };
2974 let tls = decode_tls_config(args.get(1)
2975 .ok_or_else(|| "net.serve_quic(port, tls, handler): missing tls".to_string())?)?;
2976 let handler_name = expect_str(args.get(2))?.to_string();
2977 let program = self.program.clone()
2978 .ok_or_else(|| "net.serve_quic requires a Program reference; use DefaultHandler::with_program".to_string())?;
2979 let policy = self.policy.clone();
2980 crate::quic::serve_http3_named(port, handler_name, tls, program, policy, ServeOpts::from_env())
2981 }
2982
2983 #[cfg(feature = "quic")]
2984 fn dispatch_serve_quic_fn(&self, args: Vec<Value>) -> Result<Value, String> {
2985 let port = match args.first() {
2986 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
2987 _ => return Err("net.serve_quic_fn(port, tls, handler): port must be Int 0..=65535".into()),
2988 };
2989 let tls = decode_tls_config(args.get(1)
2990 .ok_or_else(|| "net.serve_quic_fn(port, tls, handler): missing tls".to_string())?)?;
2991 let closure = match args.into_iter().nth(2) {
2992 Some(c @ Value::Closure { .. }) => c,
2993 _ => return Err("net.serve_quic_fn(port, tls, handler): handler must be a closure".into()),
2994 };
2995 let program = self.program.clone()
2996 .ok_or_else(|| "net.serve_quic_fn requires a Program reference; use DefaultHandler::with_program".to_string())?;
2997 let policy = self.policy.clone();
2998 crate::quic::serve_http3_fn(port, closure, tls, program, policy, ServeOpts::from_env())
2999 }
3000
3001 #[cfg(feature = "quic")]
3002 fn dispatch_serve_quic_routed(&self, args: Vec<Value>) -> Result<Value, String> {
3003 let port = match args.first() {
3004 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
3005 _ => return Err("net.serve_quic_routed(port, tls, routes, fallback): port must be Int 0..=65535".into()),
3006 };
3007 let tls = decode_tls_config(args.get(1)
3008 .ok_or_else(|| "net.serve_quic_routed(port, tls, routes, fallback): missing tls".to_string())?)?;
3009 let routes_val = args.get(2).cloned()
3010 .ok_or_else(|| "net.serve_quic_routed(port, tls, routes, fallback): missing routes".to_string())?;
3011 let fallback = match args.into_iter().nth(3) {
3012 Some(c @ Value::Closure { .. }) => c,
3013 _ => return Err("net.serve_quic_routed(port, tls, routes, fallback): fallback must be a closure".into()),
3014 };
3015 let routes = decode_routes_arg(routes_val)?;
3016 let program = self.program.clone()
3017 .ok_or_else(|| "net.serve_quic_routed requires a Program reference; use DefaultHandler::with_program".to_string())?;
3018 let policy = self.policy.clone();
3019 crate::quic::serve_http3_routed(port, routes, fallback, tls, program, policy, ServeOpts::from_env())
3020 }
3021
3022 #[cfg(not(feature = "quic"))]
3023 fn dispatch_serve_quic_named(&self, _args: Vec<Value>) -> Result<Value, String> {
3024 Err("net.serve_quic: lex-runtime was compiled without the `quic` feature (needed for quinn + h3)".into())
3025 }
3026 #[cfg(not(feature = "quic"))]
3027 fn dispatch_serve_quic_fn(&self, _args: Vec<Value>) -> Result<Value, String> {
3028 Err("net.serve_quic_fn: lex-runtime was compiled without the `quic` feature (needed for quinn + h3)".into())
3029 }
3030 #[cfg(not(feature = "quic"))]
3031 fn dispatch_serve_quic_routed(&self, _args: Vec<Value>) -> Result<Value, String> {
3032 Err("net.serve_quic_routed: lex-runtime was compiled without the `quic` feature (needed for quinn + h3)".into())
3033 }
3034}
3035
3036fn env_http2() -> bool {
3046 match std::env::var("LEX_NET_HTTP2") {
3047 Ok(v) => {
3048 let s = v.trim().to_ascii_lowercase();
3049 s == "1" || s == "true"
3050 }
3051 Err(_) => false,
3052 }
3053}
3054
3055pub(crate) fn build_request_value_parts(
3057 parts: &hyper::http::request::Parts,
3058 body: &bytes::Bytes,
3059) -> Value {
3060 let method = parts.method.as_str().to_string();
3061 let path = parts.uri.path().to_string();
3069 let query = parts.uri.query().map(str::to_string).unwrap_or_default();
3070 let mut headers_map = std::collections::BTreeMap::new();
3071 for (name, val) in &parts.headers {
3072 if let Ok(v) = val.to_str() {
3073 headers_map.insert(
3074 lex_bytecode::MapKey::Str(name.as_str().to_ascii_lowercase()),
3075 Value::Str(v.to_string().into()),
3076 );
3077 }
3078 }
3079 let body_str = String::from_utf8_lossy(body).into_owned();
3080 let mut rec = indexmap::IndexMap::new();
3081 rec.insert("method".into(), Value::Str(method.into()));
3082 rec.insert("path".into(), Value::Str(path.into()));
3083 rec.insert("query".into(), Value::Str(query.into()));
3084 rec.insert("body".into(), Value::Str(body_str.into()));
3085 rec.insert("headers".into(), Value::Map(headers_map));
3086 rec.insert("path_params".into(), Value::Map(std::collections::BTreeMap::new()));
3087 Value::record_dynamic(rec)
3088}
3089
3090fn build_request_value_tiny(req: &mut tiny_http::Request) -> Value {
3092 let method = format!("{:?}", req.method()).to_uppercase();
3093 let url = req.url().to_string();
3094 let (path, query) = match url.split_once('?') {
3095 Some((p, q)) => (p.to_string(), q.to_string()),
3096 None => (url, String::new()),
3097 };
3098 let mut headers_map = std::collections::BTreeMap::new();
3099 for h in req.headers() {
3100 headers_map.insert(
3101 lex_bytecode::MapKey::Str(h.field.as_str().as_str().to_ascii_lowercase()),
3102 Value::Str(h.value.as_str().to_string().into()),
3103 );
3104 }
3105 let mut body = String::new();
3106 let _ = req.as_reader().read_to_string(&mut body);
3107 let mut rec = indexmap::IndexMap::new();
3108 rec.insert("method".into(), Value::Str(method.into()));
3109 rec.insert("path".into(), Value::Str(path.into()));
3110 rec.insert("query".into(), Value::Str(query.into()));
3111 rec.insert("body".into(), Value::Str(body.into()));
3112 rec.insert("headers".into(), Value::Map(headers_map));
3113 rec.insert("path_params".into(), Value::Map(std::collections::BTreeMap::new()));
3114 Value::record_dynamic(rec)
3115}
3116
3117pub(crate) fn unpack_response(vm: &mut Vm, v: &Value) -> UnpackedResponse {
3118 if !matches!(v, Value::Record { .. } | Value::ArenaRecord { .. }) {
3124 return (
3125 500,
3126 ResponseBodyOut::Str(format!("handler returned non-record: {v:?}")),
3127 vec![],
3128 );
3129 }
3130
3131 let status = vm.get_record_field(v, "status").and_then(|s| match s {
3132 Value::Int(n) => Some(n as u16),
3133 _ => None,
3134 }).unwrap_or(200);
3135
3136 let body = match vm.get_record_field(v, "body") {
3140 Some(Value::Variant { name, mut args }) if args.len() == 1 => {
3141 let inner = args.pop().unwrap();
3142 match (name.as_str(), inner) {
3143 ("BodyStr", Value::Str(s)) => ResponseBodyOut::Str(s.to_string()),
3145 ("BodyStream", iter_v) => {
3146 let drained = materialize_lazy_iter(vm, iter_v);
3147 ResponseBodyOut::TextChunks(drain_iter_str(&drained))
3148 }
3149 ("BodyBytes", iter_v) => {
3150 let drained = materialize_lazy_iter(vm, iter_v);
3151 ResponseBodyOut::BytesChunks(drain_iter_bytes(&drained))
3152 }
3153 _ => ResponseBodyOut::Str(String::new()),
3154 }
3155 }
3156 Some(Value::Str(s)) => ResponseBodyOut::Str(s.to_string()),
3162 _ => ResponseBodyOut::Str(String::new()),
3163 };
3164
3165 let headers: Vec<(String, String)> = match vm.get_record_field(v, "headers") {
3166 Some(Value::Map(hmap)) => hmap.iter().filter_map(|(k, val)| {
3167 if let (lex_bytecode::MapKey::Str(name), Value::Str(s)) = (k, val) {
3168 Some((name.clone(), s.to_string()))
3169 } else {
3170 None
3171 }
3172 }).collect(),
3173 _ => vec![],
3174 };
3175
3176 (status, body, headers)
3177}
3178
3179type HyperRespBody =
3180 http_body_util::combinators::BoxBody<bytes::Bytes, std::convert::Infallible>;
3181
3182fn build_hyper_response(
3192 (status, body, headers): UnpackedResponse,
3193) -> hyper::Response<HyperRespBody> {
3194 use http_body_util::BodyExt as _;
3195 let boxed_body: HyperRespBody = match body {
3196 ResponseBodyOut::Str(s) => {
3197 http_body_util::Full::new(bytes::Bytes::from(s.into_bytes())).boxed()
3198 }
3199 ResponseBodyOut::TextChunks(chunks) | ResponseBodyOut::BytesChunks(chunks) => {
3200 HyperChunkedBody::from(chunks).boxed()
3201 }
3202 };
3203 let mut builder = hyper::Response::builder().status(status);
3204 for (name, val) in headers {
3205 builder = builder.header(name, val);
3206 }
3207 builder
3208 .body(boxed_body)
3209 .unwrap_or_else(|_| error_response(500, "response build error"))
3210}
3211
3212fn error_response(status: u16, msg: &str) -> hyper::Response<HyperRespBody> {
3213 use http_body_util::BodyExt as _;
3214 hyper::Response::builder()
3215 .status(status)
3216 .body(
3217 http_body_util::Full::new(bytes::Bytes::from(msg.to_owned()))
3218 .boxed(),
3219 )
3220 .unwrap_or_else(|_| {
3221 use http_body_util::BodyExt as _;
3222 hyper::Response::new(http_body_util::Empty::new().map_err(|e| match e {}).boxed())
3223 })
3224}
3225
3226struct HyperChunkedBody {
3229 chunks: std::collections::VecDeque<Vec<u8>>,
3230}
3231
3232impl From<Vec<Vec<u8>>> for HyperChunkedBody {
3233 fn from(chunks: Vec<Vec<u8>>) -> Self {
3234 Self {
3235 chunks: chunks.into_iter().filter(|c| !c.is_empty()).collect(),
3236 }
3237 }
3238}
3239
3240impl hyper::body::Body for HyperChunkedBody {
3241 type Data = bytes::Bytes;
3242 type Error = std::convert::Infallible;
3243
3244 fn poll_frame(
3245 mut self: std::pin::Pin<&mut Self>,
3246 _cx: &mut std::task::Context<'_>,
3247 ) -> std::task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
3248 match self.chunks.pop_front() {
3249 Some(chunk) => std::task::Poll::Ready(Some(Ok(hyper::body::Frame::data(
3250 bytes::Bytes::from(chunk),
3251 )))),
3252 None => std::task::Poll::Ready(None),
3253 }
3254 }
3255}
3256
3257fn respond_with_body_tls(
3261 req: tiny_http::Request,
3262 status: u16,
3263 body: ResponseBodyOut,
3264 headers: Vec<(String, String)>,
3265) {
3266 let tiny_headers: Vec<tiny_http::Header> = headers
3267 .into_iter()
3268 .filter_map(|(name, val)| format!("{name}: {val}").parse::<tiny_http::Header>().ok())
3269 .collect();
3270 match body {
3271 ResponseBodyOut::Str(s) => {
3272 let mut response = tiny_http::Response::from_string(s).with_status_code(status);
3273 for h in tiny_headers {
3274 response.add_header(h);
3275 }
3276 let _ = req.respond(response);
3277 }
3278 ResponseBodyOut::TextChunks(chunks) | ResponseBodyOut::BytesChunks(chunks) => {
3279 let reader = ChunkReader::new(chunks);
3280 let response = tiny_http::Response::new(
3281 tiny_http::StatusCode(status),
3282 tiny_headers,
3283 reader,
3284 None,
3285 None,
3286 );
3287 let _ = req.respond(response);
3288 }
3289 }
3290}
3291
3292pub(crate) type UnpackedResponse = (u16, ResponseBodyOut, Vec<(String, String)>);
3302
3303pub(crate) enum ResponseBodyOut {
3304 Str(String),
3305 TextChunks(Vec<Vec<u8>>),
3309 BytesChunks(Vec<Vec<u8>>),
3312}
3313
3314fn drain_iter_str(v: &Value) -> Vec<Vec<u8>> {
3327 match v {
3328 Value::Variant { name, args }
3329 if name == "__IterEager" && args.len() == 2 =>
3330 {
3331 if let (Value::List(items), Value::Int(idx)) = (&args[0], &args[1]) {
3332 items.iter().skip(*idx as usize).filter_map(|item| {
3333 if let Value::Str(s) = item { Some(s.as_bytes().to_vec()) } else { None }
3334 }).collect()
3335 } else {
3336 Vec::new()
3337 }
3338 }
3339 _ => Vec::new(),
3340 }
3341}
3342
3343fn drain_iter_bytes(v: &Value) -> Vec<Vec<u8>> {
3347 match v {
3348 Value::Variant { name, args }
3349 if name == "__IterEager" && args.len() == 2 =>
3350 {
3351 if let (Value::List(items), Value::Int(idx)) = (&args[0], &args[1]) {
3352 items.iter().skip(*idx as usize).filter_map(|item| {
3353 if let Value::List(ints) = item {
3354 Some(ints.iter().filter_map(|i| match i {
3355 Value::Int(n) => Some((*n & 0xff) as u8),
3356 _ => None,
3357 }).collect::<Vec<u8>>())
3358 } else {
3359 None
3360 }
3361 }).collect()
3362 } else {
3363 Vec::new()
3364 }
3365 }
3366 _ => Vec::new(),
3367 }
3368}
3369
3370fn materialize_lazy_iter(vm: &mut Vm, v: Value) -> Value {
3383 let mut current = v;
3384 let mut items: Vec<Value> = Vec::new();
3385 loop {
3386 match current {
3387 Value::Variant { name, args } if name == "__IterLazy" && args.len() == 2 => {
3388 let seed = args[0].clone();
3389 let step = args[1].clone();
3390 match vm.invoke_closure_value(step.clone(), vec![seed]) {
3391 Ok(Value::Variant { name: opt, args: opt_args })
3392 if opt == "None" =>
3393 {
3394 let _ = opt_args;
3395 break;
3396 }
3397 Ok(Value::Variant { name: opt, args: opt_args })
3398 if opt == "Some" && opt_args.len() == 1 =>
3399 {
3400 if let Value::Tuple(pair) = &opt_args[0] {
3401 if pair.len() == 2 {
3402 items.push(pair[0].clone());
3403 current = Value::Variant {
3404 name: "__IterLazy".to_string(),
3405 args: vec![pair[1].clone(), step],
3406 };
3407 continue;
3408 }
3409 }
3410 break;
3412 }
3413 _ => break,
3414 }
3415 }
3416 other => {
3419 if items.is_empty() {
3420 return other;
3421 }
3422 let _ = other;
3425 break;
3426 }
3427 }
3428 }
3429 Value::Variant {
3430 name: "__IterEager".to_string(),
3431 args: vec![
3432 Value::List(items.into_iter().collect()),
3433 Value::Int(0),
3434 ],
3435 }
3436}
3437
3438
3439struct ChunkReader {
3445 chunks: std::collections::VecDeque<Vec<u8>>,
3446 cursor: usize,
3447}
3448
3449impl ChunkReader {
3450 fn new(chunks: Vec<Vec<u8>>) -> Self {
3451 Self {
3452 chunks: chunks.into_iter().filter(|c| !c.is_empty()).collect(),
3453 cursor: 0,
3454 }
3455 }
3456}
3457
3458impl std::io::Read for ChunkReader {
3459 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
3460 loop {
3461 let Some(front) = self.chunks.front() else {
3462 return Ok(0);
3463 };
3464 let remaining = &front[self.cursor..];
3465 if remaining.is_empty() {
3466 self.chunks.pop_front();
3467 self.cursor = 0;
3468 continue;
3469 }
3470 let n = remaining.len().min(buf.len());
3471 buf[..n].copy_from_slice(&remaining[..n]);
3472 self.cursor += n;
3473 if self.cursor >= front.len() {
3474 self.chunks.pop_front();
3475 self.cursor = 0;
3476 }
3477 return Ok(n);
3478 }
3479 }
3480}
3481
3482fn http_request(method: &str, url: &str, body: Option<&str>) -> Value {
3488 use std::time::Duration;
3489 let agent: ureq::Agent = ureq::Agent::config_builder()
3494 .timeout_connect(Some(Duration::from_secs(10)))
3495 .timeout_recv_body(Some(Duration::from_secs(30)))
3496 .timeout_send_body(Some(Duration::from_secs(10)))
3497 .http_status_as_error(false)
3498 .build()
3499 .into();
3500 let resp = match (method, body) {
3501 ("GET", _) => agent.get(url).call(),
3502 ("POST", Some(b)) => agent.post(url).send(b),
3503 ("POST", None) => agent.post(url).send(""),
3504 (m, _) => return err_value(format!("unsupported method: {m}")),
3505 };
3506 match resp {
3507 Ok(mut r) => {
3508 let status = r.status().as_u16();
3509 let body = r.body_mut().read_to_string().unwrap_or_default();
3510 if (200..300).contains(&status) {
3511 Value::Variant { name: "Ok".into(), args: vec![Value::Str(body.into())] }
3512 } else {
3513 err_value(format!("status {status}: {body}"))
3514 }
3515 }
3516 Err(e) => err_value(format!("transport: {e}")),
3517 }
3518}
3519
3520fn http_stream_agent() -> ureq::Agent {
3527 use std::time::Duration;
3528 ureq::Agent::config_builder()
3529 .timeout_global(Some(Duration::from_secs(600)))
3530 .http_status_as_error(false)
3531 .build()
3532 .into()
3533}
3534
3535fn http_agent(timeout_ms: Option<u64>) -> ureq::Agent {
3548 use std::time::Duration;
3549 match timeout_ms {
3550 Some(ms) => ureq::Agent::config_builder()
3551 .timeout_global(Some(Duration::from_millis(ms)))
3552 .http_status_as_error(false)
3553 .build()
3554 .into(),
3555 None => ureq::Agent::config_builder()
3556 .timeout_connect(Some(Duration::from_secs(10)))
3557 .timeout_recv_body(Some(Duration::from_secs(30)))
3558 .timeout_send_body(Some(Duration::from_secs(10)))
3559 .http_status_as_error(false)
3560 .build()
3561 .into(),
3562 }
3563}
3564
3565fn http_error_value(e: ureq::Error) -> Value {
3569 let (ctor, payload): (&str, Option<String>) = match &e {
3570 ureq::Error::Timeout(_) => ("TimeoutError", None),
3571 ureq::Error::Tls(s) => ("TlsError", Some((*s).into())),
3572 ureq::Error::Pem(p) => ("TlsError", Some(format!("{p}"))),
3573 ureq::Error::Rustls(r) => ("TlsError", Some(format!("{r}"))),
3574 _ => ("NetworkError", Some(format!("{e}"))),
3575 };
3576 let args = match payload { Some(s) => vec![Value::Str(s.into())], None => vec![] };
3577 let inner = Value::Variant { name: ctor.into(), args };
3578 Value::Variant { name: "Err".into(), args: vec![inner] }
3579}
3580
3581fn http_decode_err(msg: String) -> Value {
3582 let inner = Value::Variant {
3583 name: "DecodeError".into(),
3584 args: vec![Value::Str(msg.into())],
3585 };
3586 Value::Variant { name: "Err".into(), args: vec![inner] }
3587}
3588
3589fn http_send_simple(
3594 method: &str,
3595 url: &str,
3596 body: Option<Vec<u8>>,
3597 content_type: &str,
3598 timeout_ms: Option<u64>,
3599) -> Value {
3600 http_send_full(method, url, body, content_type, &[], timeout_ms)
3601}
3602
3603fn http_send_full(
3604 method: &str,
3605 url: &str,
3606 body: Option<Vec<u8>>,
3607 content_type: &str,
3608 headers: &[(String, String)],
3609 timeout_ms: Option<u64>,
3610) -> Value {
3611 let agent = http_agent(timeout_ms);
3612 let method_upper = method.to_ascii_uppercase();
3618 let body_bytes: Vec<u8> = body.unwrap_or_default();
3619 let resp = match method_upper.as_str() {
3620 "GET" => {
3625 let mut req = agent.get(url);
3626 if !content_type.is_empty() { req = req.header("content-type", content_type); }
3627 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3628 req.call()
3629 }
3630 "HEAD" => {
3631 let mut req = agent.head(url);
3632 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3633 req.call()
3634 }
3635 "DELETE" => {
3636 let mut req = agent.delete(url);
3637 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3638 req.call()
3639 }
3640 "POST" => {
3645 let mut req = agent.post(url);
3646 if !content_type.is_empty() { req = req.header("content-type", content_type); }
3647 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3648 req.send(&body_bytes[..])
3649 }
3650 "PUT" => {
3651 let mut req = agent.put(url);
3652 if !content_type.is_empty() { req = req.header("content-type", content_type); }
3653 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3654 req.send(&body_bytes[..])
3655 }
3656 "PATCH" => {
3657 let mut req = agent.patch(url);
3658 if !content_type.is_empty() { req = req.header("content-type", content_type); }
3659 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3660 req.send(&body_bytes[..])
3661 }
3662 m => {
3663 return http_decode_err(format!("unsupported method: {m}"));
3664 }
3665 };
3666 match resp {
3667 Ok(mut r) => {
3668 let status = r.status().as_u16() as i64;
3669 let headers_map = collect_response_headers(r.headers());
3670 let body_bytes = match r.body_mut().with_config().limit(10 * 1024 * 1024).read_to_vec() {
3671 Ok(b) => b,
3672 Err(e) => return http_decode_err(format!("body read: {e}")),
3673 };
3674 let mut rec = indexmap::IndexMap::new();
3675 rec.insert("status".into(), Value::Int(status));
3676 rec.insert("headers".into(), Value::Map(headers_map));
3677 rec.insert("body".into(), Value::Bytes(body_bytes));
3678 Value::Variant { name: "Ok".into(), args: vec![Value::record_dynamic(rec)] }
3679 }
3680 Err(e) => http_error_value(e),
3681 }
3682}
3683
3684fn collect_response_headers(
3685 headers: &ureq::http::HeaderMap,
3686) -> std::collections::BTreeMap<lex_bytecode::MapKey, Value> {
3687 let mut out = std::collections::BTreeMap::new();
3688 for (name, value) in headers.iter() {
3689 let v = value.to_str().unwrap_or("").to_string();
3690 out.insert(lex_bytecode::MapKey::Str(name.as_str().to_string()), Value::Str(v.into()));
3691 }
3692 out
3693}
3694
3695fn http_send_record(handler: &DefaultHandler, req: &indexmap::IndexMap<smol_str::SmolStr, Value>) -> Value {
3699 let method = match req.get("method") {
3700 Some(Value::Str(s)) => s.to_string(),
3701 _ => return http_decode_err("HttpRequest.method must be Str".into()),
3702 };
3703 let url = match req.get("url") {
3704 Some(Value::Str(s)) => s.to_string(),
3705 _ => return http_decode_err("HttpRequest.url must be Str".into()),
3706 };
3707 if let Err(e) = handler.ensure_host_allowed(&url) {
3708 return http_decode_err(e);
3709 }
3710 let body = match req.get("body") {
3711 Some(Value::Variant { name, args }) if name == "None" => None,
3712 Some(Value::Variant { name, args }) if name == "Some" => match args.as_slice() {
3713 [Value::Bytes(b)] => Some(b.clone()),
3714 _ => return http_decode_err("HttpRequest.body Some payload must be Bytes".into()),
3715 },
3716 _ => return http_decode_err("HttpRequest.body must be Option[Bytes]".into()),
3717 };
3718 let timeout_ms = match req.get("timeout_ms") {
3719 Some(Value::Variant { name, .. }) if name == "None" => None,
3720 Some(Value::Variant { name, args }) if name == "Some" => match args.as_slice() {
3721 [Value::Int(n)] if *n >= 0 => Some(*n as u64),
3722 _ => return http_decode_err(
3723 "HttpRequest.timeout_ms Some payload must be a non-negative Int".into()),
3724 },
3725 _ => return http_decode_err("HttpRequest.timeout_ms must be Option[Int]".into()),
3726 };
3727 let headers: Vec<(String, String)> = match req.get("headers") {
3728 Some(Value::Map(m)) => m.iter().filter_map(|(k, v)| {
3729 let kk = match k { lex_bytecode::MapKey::Str(s) => s.clone(), _ => return None };
3730 let vv = match v { Value::Str(s) => s.to_string(), _ => return None };
3731 Some((kk, vv))
3732 }).collect(),
3733 _ => return http_decode_err("HttpRequest.headers must be Map[Str, Str]".into()),
3734 };
3735 http_send_full(&method, &url, body, "", &headers, timeout_ms)
3736}
3737
3738fn expect_record(v: Option<&Value>) -> Result<&indexmap::IndexMap<smol_str::SmolStr, Value>, String> {
3739 match v {
3740 Some(Value::Record { fields: r, .. }) => Ok(r),
3741 Some(other) => Err(format!("expected Record, got {other:?}")),
3742 None => Err("missing Record argument".into()),
3743 }
3744}
3745
3746fn err_value(msg: String) -> Value {
3747 Value::Variant { name: "Err".into(), args: vec![Value::Str(msg.into())] }
3748}
3749
3750fn expect_str(v: Option<&Value>) -> Result<&str, String> {
3751 match v {
3752 Some(Value::Str(s)) => Ok(s),
3753 Some(other) => Err(format!("expected Str arg, got {other:?}")),
3754 None => Err("missing argument".into()),
3755 }
3756}
3757
3758fn expect_int(v: Option<&Value>) -> Result<i64, String> {
3759 match v {
3760 Some(Value::Int(n)) => Ok(*n),
3761 Some(other) => Err(format!("expected Int arg, got {other:?}")),
3762 None => Err("missing argument".into()),
3763 }
3764}
3765
3766fn ok(v: Value) -> Value {
3767 Value::Variant { name: "Ok".into(), args: vec![v] }
3768}
3769fn err(v: Value) -> Value {
3770 Value::Variant { name: "Err".into(), args: vec![v] }
3771}
3772
3773fn http_stream_lines_impl(handler: &DefaultHandler, url: &str, headers_val: &Value, body: &str) -> Value {
3786 let body_bytes = body.as_bytes().to_vec();
3787 let agent = http_stream_agent();
3790 let mut req = agent.post(url);
3791 if let Value::Map(headers) = headers_val {
3792 for (k, v) in headers {
3793 let key_str = match k {
3794 lex_bytecode::MapKey::Str(s) => s.as_str(),
3795 _ => continue,
3796 };
3797 if let Value::Str(val) = v {
3798 req = req.header(key_str, val.as_str());
3799 }
3800 }
3801 }
3802 match req.send(&body_bytes[..]) {
3803 Ok(resp) => {
3804 use std::io::BufRead;
3805 let reader = std::io::BufReader::new(resp.into_body().into_reader());
3806 let lines = reader
3809 .lines()
3810 .map_while(Result::ok)
3811 .map(|l| decode_unicode_escapes(&l));
3812 let handle = handler.register_stream(lines);
3813 ok(stream_handle_value(handle))
3814 }
3815 Err(e) => err(Value::Str(format!("http.stream_lines: {e}").into())),
3816 }
3817}
3818
3819fn decode_unicode_escapes(s: &str) -> String {
3820 let mut result = String::with_capacity(s.len());
3821 let mut chars = s.chars().peekable();
3822 while let Some(c) = chars.next() {
3823 if c != '\\' {
3824 result.push(c);
3825 continue;
3826 }
3827 match chars.peek() {
3828 Some('u') => {
3829 chars.next();
3830 let hex: String = (0..4).filter_map(|_| chars.next()).collect();
3831 if hex.len() == 4 {
3832 if let Ok(n) = u32::from_str_radix(&hex, 16) {
3833 if let Some(ch) = char::from_u32(n) {
3834 result.push(ch);
3835 continue;
3836 }
3837 }
3838 }
3839 result.push('\\');
3840 result.push('u');
3841 result.push_str(&hex);
3842 }
3843 _ => result.push(c),
3844 }
3845 }
3846 result
3847}
3848
3849fn sql_error(message: impl Into<String>, code: Option<String>, detail: Option<String>) -> Value {
3853 let some = |s: String| Value::Variant { name: "Some".into(), args: vec![Value::Str(s.into())] };
3854 let none = || Value::Variant { name: "None".into(), args: vec![] };
3855 let mut rec = indexmap::IndexMap::new();
3856 let msg: String = message.into();
3857 rec.insert("message".into(), Value::Str(msg.into()));
3858 rec.insert("code".into(), match code {
3859 Some(c) => some(c),
3860 None => none(),
3861 });
3862 rec.insert("detail".into(), match detail {
3863 Some(d) => some(d),
3864 None => none(),
3865 });
3866 Value::record_dynamic(rec)
3867}
3868
3869fn sqlite_err_to_sql_error(e: rusqlite::Error, op: &str) -> Value {
3879 let message = format!("{op}: {e}");
3880 match &e {
3881 rusqlite::Error::SqliteFailure(ffi, detail_opt) => {
3882 sql_error(
3883 message,
3884 Some(sqlite_extended_code_name(ffi.extended_code)),
3885 detail_opt.clone(),
3886 )
3887 }
3888 rusqlite::Error::SqlInputError { error, msg, .. } => {
3889 sql_error(
3890 message,
3891 Some(sqlite_extended_code_name(error.extended_code)),
3892 Some(msg.clone()),
3893 )
3894 }
3895 _ => sql_error(message, None, None),
3896 }
3897}
3898
3899fn sqlite_extended_code_name(code: i32) -> String {
3905 use rusqlite::ffi::*;
3906 let s = match code {
3907 SQLITE_BUSY => "SQLITE_BUSY",
3908 SQLITE_LOCKED => "SQLITE_LOCKED",
3909 SQLITE_READONLY => "SQLITE_READONLY",
3910 SQLITE_IOERR => "SQLITE_IOERR",
3911 SQLITE_CORRUPT => "SQLITE_CORRUPT",
3912 SQLITE_NOTFOUND => "SQLITE_NOTFOUND",
3913 SQLITE_FULL => "SQLITE_FULL",
3914 SQLITE_CANTOPEN => "SQLITE_CANTOPEN",
3915 SQLITE_PROTOCOL => "SQLITE_PROTOCOL",
3916 SQLITE_SCHEMA => "SQLITE_SCHEMA",
3917 SQLITE_TOOBIG => "SQLITE_TOOBIG",
3918 SQLITE_CONSTRAINT => "SQLITE_CONSTRAINT",
3919 SQLITE_CONSTRAINT_CHECK => "SQLITE_CONSTRAINT_CHECK",
3920 SQLITE_CONSTRAINT_FOREIGNKEY => "SQLITE_CONSTRAINT_FOREIGNKEY",
3921 SQLITE_CONSTRAINT_NOTNULL => "SQLITE_CONSTRAINT_NOTNULL",
3922 SQLITE_CONSTRAINT_PRIMARYKEY => "SQLITE_CONSTRAINT_PRIMARYKEY",
3923 SQLITE_CONSTRAINT_TRIGGER => "SQLITE_CONSTRAINT_TRIGGER",
3924 SQLITE_CONSTRAINT_UNIQUE => "SQLITE_CONSTRAINT_UNIQUE",
3925 SQLITE_CONSTRAINT_VTAB => "SQLITE_CONSTRAINT_VTAB",
3926 SQLITE_CONSTRAINT_ROWID => "SQLITE_CONSTRAINT_ROWID",
3927 SQLITE_MISMATCH => "SQLITE_MISMATCH",
3928 SQLITE_RANGE => "SQLITE_RANGE",
3929 SQLITE_NOTADB => "SQLITE_NOTADB",
3930 SQLITE_AUTH => "SQLITE_AUTH",
3931 _ => return format!("SQLITE_ERROR_{code}"),
3932 };
3933 s.to_string()
3934}
3935
3936fn pg_err_to_sql_error(e: postgres::Error, op: &str) -> Value {
3940 let message = format!("{op}: {e}");
3941 let code = e.as_db_error().map(|db| db.code().code().to_string());
3942 let detail = e.as_db_error().and_then(|db| db.detail().map(|s| s.to_string()));
3943 sql_error(message, code, detail)
3944}
3945
3946impl DefaultHandler {
3947 fn dispatch_call_mcp(&mut self, args: Vec<Value>) -> Value {
3953 let server = match args.first() {
3954 Some(Value::Str(s)) => s.clone(),
3955 _ => return err(Value::Str(
3956 "agent.call_mcp(server, tool, args_json): server must be Str".into())),
3957 };
3958 let tool = match args.get(1) {
3959 Some(Value::Str(s)) => s.clone(),
3960 _ => return err(Value::Str(
3961 "agent.call_mcp(server, tool, args_json): tool must be Str".into())),
3962 };
3963 let args_json = match args.get(2) {
3964 Some(Value::Str(s)) => s.clone(),
3965 _ => return err(Value::Str(
3966 "agent.call_mcp(server, tool, args_json): args_json must be Str".into())),
3967 };
3968 let parsed: serde_json::Value = match serde_json::from_str(&args_json) {
3969 Ok(v) => v,
3970 Err(e) => return err(Value::Str(format!(
3971 "agent.call_mcp: args_json is not valid JSON: {e}").into())),
3972 };
3973 match self.mcp_clients.call(&server, &tool, parsed) {
3974 Ok(result) => ok(Value::Str(
3975 serde_json::to_string(&result).unwrap_or_else(|_| "null".into()).into())),
3976 Err(e) => err(Value::Str(e.into())),
3977 }
3978 }
3979
3980 fn dispatch_cloud_stream(&mut self, args: Vec<Value>) -> Value {
3986 let _prompt = match args.first() {
3987 Some(Value::Str(s)) => s.clone(),
3988 _ => return err(Value::Str(
3989 "agent.cloud_stream(prompt): prompt must be Str".into())),
3990 };
3991 let chunks: Vec<String> = match std::env::var("LEX_LLM_STREAM_FIXTURE") {
3992 Ok(v) => v.split('|').map(|s| s.to_string()).collect(),
3993 Err(_) => return err(Value::Str(
3994 "agent.cloud_stream: live streaming not yet implemented; \
3995 set LEX_LLM_STREAM_FIXTURE='chunk1|chunk2|…' for tests".into())),
3996 };
3997 let handle = self.register_stream(chunks.into_iter());
3998 ok(stream_handle_value(handle))
3999 }
4000
4001 fn dispatch_stream_next(&mut self, args: Vec<Value>) -> Value {
4007 let handle = match args.first().and_then(stream_handle_id) {
4008 Some(h) => h,
4009 None => return Value::Variant { name: "None".into(), args: vec![] },
4010 };
4011 let mut streams = match self.streams.lock() {
4012 Ok(g) => g,
4013 Err(_) => return Value::Variant { name: "None".into(), args: vec![] },
4014 };
4015 match streams.get_mut(&handle).and_then(|it| it.next()) {
4016 Some(chunk) => some(Value::Str(chunk.into())),
4017 None => {
4018 streams.remove(&handle);
4019 Value::Variant { name: "None".into(), args: vec![] }
4020 }
4021 }
4022 }
4023
4024 fn dispatch_stream_collect(&mut self, args: Vec<Value>) -> Value {
4029 let handle = match args.first().and_then(stream_handle_id) {
4030 Some(h) => h,
4031 None => return Value::List(std::collections::VecDeque::new()),
4032 };
4033 let mut iter = {
4034 let mut streams = match self.streams.lock() {
4035 Ok(g) => g,
4036 Err(_) => return Value::List(std::collections::VecDeque::new()),
4037 };
4038 match streams.remove(&handle) {
4039 Some(it) => it,
4040 None => return Value::List(std::collections::VecDeque::new()),
4041 }
4042 };
4043 let mut out: std::collections::VecDeque<Value> = std::collections::VecDeque::new();
4044 for chunk in iter.by_ref() {
4045 out.push_back(Value::Str(chunk.into()));
4046 }
4047 Value::List(out)
4048 }
4049
4050 fn register_stream<I>(&self, iter: I) -> String
4054 where
4055 I: Iterator<Item = String> + Send + 'static,
4056 {
4057 let id = self
4058 .next_stream_id
4059 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4060 let handle = format!("stream_{id}");
4061 if let Ok(mut streams) = self.streams.lock() {
4062 streams.insert(handle.clone(), Box::new(iter));
4063 }
4064 handle
4065 }
4066}
4067
4068fn stream_handle_value(handle: String) -> Value {
4073 Value::Variant {
4074 name: "__StreamHandle".into(),
4075 args: vec![Value::Str(handle.into())],
4076 }
4077}
4078
4079fn stream_handle_id(v: &Value) -> Option<String> {
4083 match v {
4084 Value::Variant { name, args } if name == "__StreamHandle" => match args.first() {
4085 Some(Value::Str(h)) => Some(h.to_string()),
4086 _ => None,
4087 },
4088 _ => None,
4089 }
4090}
4091
4092fn dispatch_llm_local(args: Vec<Value>) -> Value {
4097 let prompt = match args.first() {
4098 Some(Value::Str(s)) => s.clone(),
4099 _ => return err(Value::Str(
4100 "agent.local_complete(prompt): prompt must be Str".into())),
4101 };
4102 match crate::llm::local_complete(&prompt) {
4103 Ok(text) => ok(Value::Str(text.into())),
4104 Err(e) => err(Value::Str(e.into())),
4105 }
4106}
4107
4108fn dispatch_llm_cloud(args: Vec<Value>) -> Value {
4115 let prompt = match args.first() {
4116 Some(Value::Str(s)) => s.clone(),
4117 _ => return err(Value::Str(
4118 "agent.cloud_complete(prompt): prompt must be Str".into())),
4119 };
4120 match crate::llm::cloud_complete(&prompt) {
4121 Ok(text) => ok(Value::Str(text.into())),
4122 Err(e) => err(Value::Str(e.into())),
4123 }
4124}
4125
4126fn some(v: Value) -> Value {
4127 Value::Variant { name: "Some".into(), args: vec![v] }
4128}
4129fn none() -> Value {
4130 Value::Variant { name: "None".into(), args: vec![] }
4131}
4132
4133fn expect_bytes(v: Option<&Value>) -> Result<&Vec<u8>, String> {
4134 match v {
4135 Some(Value::Bytes(b)) => Ok(b),
4136 Some(other) => Err(format!("expected Bytes arg, got {other:?}")),
4137 None => Err("missing argument".into()),
4138 }
4139}
4140
4141fn expect_kv_handle(v: Option<&Value>) -> Result<u64, String> {
4142 match v {
4143 Some(Value::Int(n)) if *n >= 0 => Ok(*n as u64),
4144 Some(other) => Err(format!("expected Kv handle (Int), got {other:?}")),
4145 None => Err("missing Kv argument".into()),
4146 }
4147}
4148
4149fn expect_sql_handle(v: Option<&Value>) -> Result<u64, String> {
4150 match v {
4151 Some(Value::Int(n)) if *n >= 0 => Ok(*n as u64),
4152 Some(other) => Err(format!("expected Db handle (Int), got {other:?}")),
4153 None => Err("missing Db argument".into()),
4154 }
4155}
4156
4157#[allow(dead_code)]
4158fn expect_str_list(v: Option<&Value>) -> Result<Vec<String>, String> {
4159 match v {
4160 Some(Value::List(items)) => items.iter().map(|x| match x {
4161 Value::Str(s) => Ok(s.to_string()),
4162 other => Err(format!("expected List[Str] element, got {other:?}")),
4163 }).collect(),
4164 Some(other) => Err(format!("expected List[Str], got {other:?}")),
4165 None => Err("missing List[Str] argument".into()),
4166 }
4167}
4168
4169fn expect_sql_params(v: Option<&Value>) -> Result<Vec<SqlParamValue>, String> {
4172 let items = match v {
4173 Some(Value::List(xs)) => xs,
4174 Some(other) => return Err(format!("expected List[SqlParam], got {other:?}")),
4175 None => return Err("missing params argument".into()),
4176 };
4177 items.iter().map(|item| {
4178 match item {
4179 Value::Variant { name, args } => match name.as_str() {
4180 "PStr" => match args.first() {
4181 Some(Value::Str(s)) => Ok(SqlParamValue::Text(s.to_string())),
4182 _ => Err("PStr requires a Str argument".into()),
4183 },
4184 "PInt" => match args.first() {
4185 Some(Value::Int(n)) => Ok(SqlParamValue::Integer(*n)),
4186 _ => Err("PInt requires an Int argument".into()),
4187 },
4188 "PFloat" => match args.first() {
4189 Some(Value::Float(f)) => Ok(SqlParamValue::Real(*f)),
4190 _ => Err("PFloat requires a Float argument".into()),
4191 },
4192 "PBool" => match args.first() {
4193 Some(Value::Bool(b)) => Ok(SqlParamValue::Bool(*b)),
4194 _ => Err("PBool requires a Bool argument".into()),
4195 },
4196 "PNull" => Ok(SqlParamValue::Null),
4197 other => Err(format!("unknown SqlParam constructor `{other}`")),
4198 },
4199 Value::Str(s) => Ok(SqlParamValue::Text(s.to_string())),
4201 other => Err(format!("expected SqlParam variant, got {other:?}")),
4202 }
4203 }).collect()
4204}
4205
4206fn sqlite_params(params: &[SqlParamValue]) -> Vec<rusqlite::types::Value> {
4208 params.iter().map(|p| match p {
4209 SqlParamValue::Text(s) => rusqlite::types::Value::Text(s.clone()),
4210 SqlParamValue::Integer(n) => rusqlite::types::Value::Integer(*n),
4211 SqlParamValue::Real(f) => rusqlite::types::Value::Real(*f),
4212 SqlParamValue::Bool(b) => rusqlite::types::Value::Integer(*b as i64),
4213 SqlParamValue::Null => rusqlite::types::Value::Null,
4214 }).collect()
4215}
4216
4217fn pg_param_refs(params: &[SqlParamValue]) -> Vec<Box<dyn postgres::types::ToSql + Sync>> {
4219 params.iter().map(|p| -> Box<dyn postgres::types::ToSql + Sync> {
4220 match p {
4221 SqlParamValue::Text(s) => Box::new(s.clone()),
4222 SqlParamValue::Integer(n) => Box::new(*n),
4223 SqlParamValue::Real(f) => Box::new(*f),
4224 SqlParamValue::Bool(b) => Box::new(*b),
4225 SqlParamValue::Null => Box::new(Option::<String>::None),
4226 }
4227 }).collect()
4228}
4229
4230fn sql_run_query_sqlite(
4232 conn: &rusqlite::Connection,
4233 stmt_str: &str,
4234 params: &[SqlParamValue],
4235) -> Value {
4236 let mut stmt = match conn.prepare(stmt_str) {
4237 Ok(s) => s,
4238 Err(e) => return err(sqlite_err_to_sql_error(e, "sql.query")),
4239 };
4240 let column_count = stmt.column_count();
4241 let column_names: Vec<String> = (0..column_count)
4242 .map(|i| stmt.column_name(i).unwrap_or("").to_string())
4243 .collect();
4244 let bound = sqlite_params(params);
4245 let bind: Vec<&dyn rusqlite::ToSql> = bound.iter()
4246 .map(|p| p as &dyn rusqlite::ToSql)
4247 .collect();
4248 let mut rows = match stmt.query(rusqlite::params_from_iter(bind.iter())) {
4249 Ok(r) => r,
4250 Err(e) => return err(sqlite_err_to_sql_error(e, "sql.query")),
4251 };
4252 let mut out: Vec<Value> = Vec::new();
4253 loop {
4254 let row = match rows.next() {
4255 Ok(Some(r)) => r,
4256 Ok(None) => break,
4257 Err(e) => return err(sqlite_err_to_sql_error(e, "sql.query")),
4258 };
4259 let mut rec = indexmap::IndexMap::new();
4260 for (i, name) in column_names.iter().enumerate() {
4261 let cell = match row.get_ref(i) {
4262 Ok(c) => sql_value_ref_to_lex(c),
4263 Err(e) => return err(sqlite_err_to_sql_error(e, &format!("sql.query: column {i}"))),
4264 };
4265 rec.insert(name.clone(), cell);
4266 }
4267 out.push(Value::record_dynamic(rec));
4268 }
4269 ok(Value::List(out.into()))
4270}
4271
4272fn sql_run_query_pg(
4274 client: &mut postgres::Client,
4275 stmt_str: &str,
4276 params: &[SqlParamValue],
4277) -> Value {
4278 let pg = pg_param_refs(params);
4279 let refs: Vec<&(dyn postgres::types::ToSql + Sync)> =
4280 pg.iter().map(|b| b.as_ref()).collect();
4281 let rows = match client.query(stmt_str, &refs) {
4282 Ok(r) => r,
4283 Err(e) => return err(pg_err_to_sql_error(e, "sql.query")),
4284 };
4285 let out: std::collections::VecDeque<Value> = rows.iter().map(|row| {
4286 Value::record_dynamic(pg_row_to_lex_record(row))
4287 }).collect();
4288 ok(Value::List(out))
4289}
4290
4291fn pg_row_to_lex_record(row: &postgres::Row) -> indexmap::IndexMap<String, Value> {
4293 use postgres::types::Type;
4294 let mut rec = indexmap::IndexMap::new();
4295 for (i, col) in row.columns().iter().enumerate() {
4296 let ty = col.type_();
4297 let val = if *ty == Type::INT2 || *ty == Type::INT4 || *ty == Type::INT8 {
4298 row.get::<_, Option<i64>>(i).map(Value::Int).unwrap_or(Value::Unit)
4299 } else if *ty == Type::FLOAT4 || *ty == Type::FLOAT8 {
4300 row.get::<_, Option<f64>>(i).map(Value::Float).unwrap_or(Value::Unit)
4301 } else if *ty == Type::BOOL {
4302 row.get::<_, Option<bool>>(i).map(Value::Bool).unwrap_or(Value::Unit)
4303 } else if *ty == Type::BYTEA {
4304 row.get::<_, Option<Vec<u8>>>(i).map(Value::Bytes).unwrap_or(Value::Unit)
4305 } else {
4306 row.get::<_, Option<String>>(i).map(|s| Value::Str(s.into())).unwrap_or(Value::Unit)
4307 };
4308 rec.insert(col.name().to_string(), val);
4309 }
4310 rec
4311}
4312
4313fn sql_get_col<F>(args: &[Value], convert: F) -> Result<Value, String>
4315where
4316 F: Fn(&Value) -> Option<Value>,
4317{
4318 let row = args.first().ok_or("sql.get_*: missing row argument")?;
4319 let col = match args.get(1) {
4320 Some(Value::Str(s)) => s.as_str(),
4321 Some(other) => return Err(format!("sql.get_*: column name must be Str, got {other:?}")),
4322 None => return Err("sql.get_*: missing column name argument".into()),
4323 };
4324 let cell = match row {
4325 Value::Record { fields: rec, .. } => rec.get(col).cloned(),
4326 other => return Err(format!("sql.get_*: row must be a Record, got {other:?}")),
4327 };
4328 Ok(match cell.and_then(|v| convert(&v)) {
4329 Some(v) => Value::Variant { name: "Some".into(), args: vec![v] },
4330 None => Value::Variant { name: "None".into(), args: vec![] },
4331 })
4332}
4333
4334fn sql_value_ref_to_lex(v: rusqlite::types::ValueRef<'_>) -> Value {
4335 use rusqlite::types::ValueRef;
4336 match v {
4337 ValueRef::Null => Value::Unit,
4338 ValueRef::Integer(n) => Value::Int(n),
4339 ValueRef::Real(f) => Value::Float(f),
4340 ValueRef::Text(s) => Value::Str(String::from_utf8_lossy(s).into_owned().into()),
4341 ValueRef::Blob(b) => Value::Bytes(b.to_vec()),
4342 }
4343}
4344
4345#[derive(Clone, Copy, PartialEq, PartialOrd)]
4348enum LogLevel { Debug, Info, Warn, Error }
4349
4350#[derive(Clone, Copy, PartialEq)]
4351enum LogFormat { Text, Json }
4352
4353#[derive(Clone)]
4354enum LogSink {
4355 Stderr,
4356 File(std::sync::Arc<Mutex<std::fs::File>>),
4357}
4358
4359struct LogState {
4360 level: LogLevel,
4361 format: LogFormat,
4362 sink: LogSink,
4363}
4364
4365fn log_state() -> &'static Mutex<LogState> {
4366 static STATE: OnceLock<Mutex<LogState>> = OnceLock::new();
4367 STATE.get_or_init(|| Mutex::new(LogState {
4368 level: LogLevel::Info,
4369 format: LogFormat::Text,
4370 sink: LogSink::Stderr,
4371 }))
4372}
4373
4374fn parse_log_level(s: &str) -> Option<LogLevel> {
4375 match s {
4376 "debug" => Some(LogLevel::Debug),
4377 "info" => Some(LogLevel::Info),
4378 "warn" => Some(LogLevel::Warn),
4379 "error" => Some(LogLevel::Error),
4380 _ => None,
4381 }
4382}
4383
4384fn level_label(l: LogLevel) -> &'static str {
4385 match l {
4386 LogLevel::Debug => "debug",
4387 LogLevel::Info => "info",
4388 LogLevel::Warn => "warn",
4389 LogLevel::Error => "error",
4390 }
4391}
4392
4393fn emit_log(level: LogLevel, msg: &str) {
4394 let state = log_state().lock().unwrap();
4395 if level < state.level {
4396 return;
4397 }
4398 let ts = chrono::Utc::now().to_rfc3339();
4399 let line = match state.format {
4400 LogFormat::Text => format!("[{}] {}: {}\n", ts, level_label(level), msg),
4401 LogFormat::Json => {
4402 let escaped = msg
4406 .replace('\\', "\\\\")
4407 .replace('"', "\\\"")
4408 .replace('\n', "\\n")
4409 .replace('\r', "\\r");
4410 format!(
4411 "{{\"ts\":\"{ts}\",\"level\":\"{}\",\"msg\":\"{escaped}\"}}\n",
4412 level_label(level),
4413 )
4414 }
4415 };
4416 let sink = state.sink.clone();
4417 drop(state);
4418 match sink {
4419 LogSink::Stderr => {
4420 use std::io::Write;
4421 let _ = std::io::stderr().write_all(line.as_bytes());
4422 }
4423 LogSink::File(f) => {
4424 use std::io::Write;
4425 if let Ok(mut g) = f.lock() {
4426 let _ = g.write_all(line.as_bytes());
4427 }
4428 }
4429 }
4430}
4431
4432pub(crate) struct ProcessState {
4433 child: std::process::Child,
4434 stdout: Option<std::io::BufReader<std::process::ChildStdout>>,
4435 stderr: Option<std::io::BufReader<std::process::ChildStderr>>,
4436}
4437
4438fn process_registry() -> &'static Mutex<ProcessRegistry> {
4452 static REGISTRY: OnceLock<Mutex<ProcessRegistry>> = OnceLock::new();
4453 REGISTRY.get_or_init(|| Mutex::new(ProcessRegistry::with_capacity(MAX_PROCESS_HANDLES)))
4454}
4455
4456const MAX_PROCESS_HANDLES: usize = 256;
4457
4458type SharedProcessState = Arc<Mutex<ProcessState>>;
4459
4460pub(crate) struct ProcessRegistry {
4461 entries: indexmap::IndexMap<u64, SharedProcessState>,
4462 cap: usize,
4463}
4464
4465impl ProcessRegistry {
4466 pub(crate) fn with_capacity(cap: usize) -> Self {
4467 Self { entries: indexmap::IndexMap::new(), cap }
4468 }
4469
4470 pub(crate) fn insert(&mut self, handle: u64, state: ProcessState) {
4474 if self.entries.len() >= self.cap {
4475 self.entries.shift_remove_index(0);
4476 }
4477 self.entries.insert(handle, Arc::new(Mutex::new(state)));
4478 }
4479
4480 pub(crate) fn touch_get(&mut self, handle: u64) -> Option<SharedProcessState> {
4484 let idx = self.entries.get_index_of(&handle)?;
4485 self.entries.move_index(idx, self.entries.len() - 1);
4486 self.entries.get(&handle).cloned()
4487 }
4488
4489 pub(crate) fn remove(&mut self, handle: u64) {
4494 self.entries.shift_remove(&handle);
4495 }
4496
4497 #[cfg(test)]
4498 pub(crate) fn len(&self) -> usize { self.entries.len() }
4499}
4500
4501fn next_process_handle() -> u64 {
4502 static COUNTER: AtomicU64 = AtomicU64::new(1);
4503 COUNTER.fetch_add(1, Ordering::SeqCst)
4504}
4505
4506#[cfg(all(test, unix))]
4507mod process_registry_tests {
4508 use super::{ProcessRegistry, ProcessState};
4509
4510 fn fresh_state() -> ProcessState {
4514 let child = std::process::Command::new("true")
4515 .stdout(std::process::Stdio::null())
4516 .stderr(std::process::Stdio::null())
4517 .spawn()
4518 .expect("spawn `true`");
4519 ProcessState { child, stdout: None, stderr: None }
4520 }
4521
4522 #[test]
4523 fn insert_and_get_round_trip() {
4524 let mut r = ProcessRegistry::with_capacity(4);
4525 r.insert(1, fresh_state());
4526 assert!(r.touch_get(1).is_some());
4527 assert!(r.touch_get(2).is_none());
4528 }
4529
4530 #[test]
4531 fn touch_get_returns_distinct_arcs_for_distinct_handles() {
4532 let mut r = ProcessRegistry::with_capacity(4);
4533 r.insert(1, fresh_state());
4534 r.insert(2, fresh_state());
4535 let a = r.touch_get(1).unwrap();
4536 let b = r.touch_get(2).unwrap();
4537 assert!(!std::sync::Arc::ptr_eq(&a, &b));
4539 }
4540
4541 #[test]
4542 fn cap_evicts_lru_on_overflow() {
4543 let mut r = ProcessRegistry::with_capacity(2);
4544 r.insert(1, fresh_state());
4545 r.insert(2, fresh_state());
4546 let _ = r.touch_get(1);
4547 r.insert(3, fresh_state());
4548 assert!(r.touch_get(1).is_some(), "1 was MRU, should survive");
4549 assert!(r.touch_get(2).is_none(), "2 was LRU, should be evicted");
4550 assert!(r.touch_get(3).is_some(), "3 just inserted, should survive");
4551 assert_eq!(r.len(), 2);
4552 }
4553
4554 #[test]
4555 fn cap_with_no_touches_evicts_in_insertion_order() {
4556 let mut r = ProcessRegistry::with_capacity(2);
4557 r.insert(10, fresh_state());
4558 r.insert(20, fresh_state());
4559 r.insert(30, fresh_state());
4560 assert!(r.touch_get(10).is_none());
4561 assert!(r.touch_get(20).is_some());
4562 assert!(r.touch_get(30).is_some());
4563 }
4564
4565 #[test]
4566 fn remove_drops_entry() {
4567 let mut r = ProcessRegistry::with_capacity(4);
4568 r.insert(1, fresh_state());
4569 r.remove(1);
4570 assert!(r.touch_get(1).is_none());
4571 assert_eq!(r.len(), 0);
4572 }
4573
4574 #[test]
4575 fn many_inserts_stay_bounded_at_cap() {
4576 let cap = 8;
4577 let mut r = ProcessRegistry::with_capacity(cap);
4578 for i in 0..(cap as u64 * 3) {
4579 r.insert(i, fresh_state());
4580 assert!(r.len() <= cap);
4581 }
4582 assert_eq!(r.len(), cap);
4583 }
4584
4585 #[test]
4586 fn outstanding_arc_outlives_remove() {
4587 let mut r = ProcessRegistry::with_capacity(4);
4591 r.insert(1, fresh_state());
4592 let arc = r.touch_get(1).expect("entry exists");
4593 r.remove(1);
4594 assert!(r.touch_get(1).is_none());
4596 let _state = arc.lock().unwrap();
4597 }
4598}
4599
4600fn expect_process_handle(v: Option<&Value>) -> Result<u64, String> {
4601 match v {
4602 Some(Value::Int(n)) if *n >= 0 => Ok(*n as u64),
4603 Some(other) => Err(format!("expected ProcessHandle (Int), got {other:?}")),
4604 None => Err("missing ProcessHandle argument".into()),
4605 }
4606}
4607
4608fn kv_registry() -> &'static Mutex<KvRegistry> {
4620 static REGISTRY: OnceLock<Mutex<KvRegistry>> = OnceLock::new();
4621 REGISTRY.get_or_init(|| Mutex::new(KvRegistry::with_capacity(MAX_KV_HANDLES)))
4622}
4623
4624const MAX_KV_HANDLES: usize = 256;
4630
4631pub(crate) struct KvRegistry {
4636 entries: indexmap::IndexMap<u64, sled::Db>,
4637 cap: usize,
4638}
4639
4640impl KvRegistry {
4641 pub(crate) fn with_capacity(cap: usize) -> Self {
4642 Self { entries: indexmap::IndexMap::new(), cap }
4643 }
4644
4645 pub(crate) fn insert(&mut self, handle: u64, db: sled::Db) {
4648 if self.entries.len() >= self.cap {
4649 self.entries.shift_remove_index(0);
4650 }
4651 self.entries.insert(handle, db);
4652 }
4653
4654 pub(crate) fn touch_get(&mut self, handle: u64) -> Option<&sled::Db> {
4656 let idx = self.entries.get_index_of(&handle)?;
4657 self.entries.move_index(idx, self.entries.len() - 1);
4658 self.entries.get(&handle)
4659 }
4660
4661 pub(crate) fn remove(&mut self, handle: u64) {
4663 self.entries.shift_remove(&handle);
4664 }
4665
4666 #[cfg(test)]
4667 pub(crate) fn len(&self) -> usize { self.entries.len() }
4668}
4669
4670fn next_kv_handle() -> u64 {
4671 static COUNTER: AtomicU64 = AtomicU64::new(1);
4672 COUNTER.fetch_add(1, Ordering::SeqCst)
4673}
4674
4675struct RedisEntry {
4690 url: String,
4691 conn: redis::Connection,
4692}
4693
4694struct RedisRegistry {
4695 entries: indexmap::IndexMap<u64, RedisEntry>,
4696 cap: usize,
4697}
4698
4699impl RedisRegistry {
4700 fn with_capacity(cap: usize) -> Self {
4701 Self { entries: indexmap::IndexMap::new(), cap }
4702 }
4703
4704 fn insert(&mut self, handle: u64, entry: RedisEntry) {
4705 if self.entries.len() >= self.cap {
4706 self.entries.shift_remove_index(0);
4707 }
4708 self.entries.insert(handle, entry);
4709 }
4710
4711 fn touch_get_mut(&mut self, handle: u64) -> Option<&mut RedisEntry> {
4712 let idx = self.entries.get_index_of(&handle)?;
4713 self.entries.move_index(idx, self.entries.len() - 1);
4714 self.entries.get_mut(&handle)
4715 }
4716
4717 fn get_url(&self, handle: u64) -> Option<String> {
4720 self.entries.get(&handle).map(|e| e.url.clone())
4721 }
4722
4723 fn remove(&mut self, handle: u64) {
4724 self.entries.shift_remove(&handle);
4725 }
4726}
4727
4728fn redis_registry() -> &'static Mutex<RedisRegistry> {
4729 static REGISTRY: OnceLock<Mutex<RedisRegistry>> = OnceLock::new();
4730 REGISTRY.get_or_init(|| Mutex::new(RedisRegistry::with_capacity(MAX_REDIS_HANDLES)))
4731}
4732
4733const MAX_REDIS_HANDLES: usize = 256;
4734
4735fn next_redis_handle() -> u64 {
4736 static COUNTER: AtomicU64 = AtomicU64::new(1);
4737 COUNTER.fetch_add(1, Ordering::SeqCst)
4738}
4739
4740fn expect_redis_handle(v: Option<&Value>) -> Result<u64, String> {
4741 match v {
4742 Some(Value::Int(n)) if *n >= 0 => Ok(*n as u64),
4743 Some(other) => Err(format!("expected ConnRedis (Int), got {other:?}")),
4744 None => Err("missing ConnRedis argument".into()),
4745 }
4746}
4747
4748fn sql_registry() -> &'static Mutex<SqlRegistry> {
4755 static REGISTRY: OnceLock<Mutex<SqlRegistry>> = OnceLock::new();
4756 REGISTRY.get_or_init(|| Mutex::new(SqlRegistry::with_capacity(MAX_SQL_HANDLES)))
4757}
4758
4759const MAX_SQL_HANDLES: usize = 256;
4760
4761const CURSOR_CHANNEL_CAPACITY: usize = 64;
4784const MAX_CURSOR_HANDLES: usize = 256;
4785
4786type CursorReceiver = std::sync::mpsc::Receiver<Result<Value, String>>;
4787
4788pub(crate) struct CursorRegistry {
4789 entries: indexmap::IndexMap<u64, Arc<Mutex<CursorReceiver>>>,
4794 cap: usize,
4795}
4796
4797impl CursorRegistry {
4798 pub(crate) fn with_capacity(cap: usize) -> Self {
4799 Self { entries: indexmap::IndexMap::new(), cap }
4800 }
4801
4802 pub(crate) fn insert(&mut self, handle: u64, rx: CursorReceiver) {
4803 if self.entries.len() >= self.cap {
4804 self.entries.shift_remove_index(0);
4805 }
4806 self.entries.insert(handle, Arc::new(Mutex::new(rx)));
4807 }
4808
4809 pub(crate) fn touch_get(&mut self, handle: u64) -> Option<Arc<Mutex<CursorReceiver>>> {
4810 let idx = self.entries.get_index_of(&handle)?;
4811 self.entries.move_index(idx, self.entries.len() - 1);
4812 self.entries.get(&handle).cloned()
4813 }
4814
4815 pub(crate) fn remove(&mut self, handle: u64) {
4816 self.entries.shift_remove(&handle);
4817 }
4818}
4819
4820fn cursor_registry() -> &'static Mutex<CursorRegistry> {
4821 static REGISTRY: OnceLock<Mutex<CursorRegistry>> = OnceLock::new();
4822 REGISTRY.get_or_init(|| Mutex::new(CursorRegistry::with_capacity(MAX_CURSOR_HANDLES)))
4823}
4824
4825fn next_cursor_handle() -> u64 {
4826 static COUNTER: AtomicU64 = AtomicU64::new(1);
4827 COUNTER.fetch_add(1, Ordering::SeqCst)
4828}
4829
4830fn sqlite_cursor_producer(
4836 conn_arc: Arc<Mutex<SqlConn>>,
4837 stmt_str: String,
4838 params: Vec<SqlParamValue>,
4839 sender: std::sync::mpsc::SyncSender<Result<Value, String>>,
4840) {
4841 let mut conn_guard = match conn_arc.lock() {
4842 Ok(g) => g,
4843 Err(p) => p.into_inner(),
4844 };
4845 let SqlConn::Sqlite(c) = &mut *conn_guard else {
4846 let _ = sender.send(Err("sqlite_cursor_producer called on non-sqlite conn".into()));
4847 return;
4848 };
4849 let mut stmt = match c.prepare(&stmt_str) {
4850 Ok(s) => s,
4851 Err(e) => { let _ = sender.send(Err(format!("prepare: {e}"))); return; }
4852 };
4853 let column_count = stmt.column_count();
4854 let column_names: Vec<String> = (0..column_count)
4855 .map(|i| stmt.column_name(i).unwrap_or("").to_string())
4856 .collect();
4857 let bound = sqlite_params(¶ms);
4858 let bind: Vec<&dyn rusqlite::ToSql> =
4859 bound.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
4860 let mut rows = match stmt.query(rusqlite::params_from_iter(bind.iter())) {
4861 Ok(r) => r,
4862 Err(e) => { let _ = sender.send(Err(format!("query: {e}"))); return; }
4863 };
4864 loop {
4865 match rows.next() {
4866 Ok(None) => break,
4867 Err(e) => {
4868 let _ = sender.send(Err(format!("row: {e}")));
4869 break;
4870 }
4871 Ok(Some(row)) => {
4872 let mut rec = indexmap::IndexMap::new();
4873 for (i, name) in column_names.iter().enumerate() {
4874 let val = match row.get_ref(i) {
4875 Ok(vr) => sql_value_ref_to_lex(vr),
4876 Err(_) => Value::Unit,
4877 };
4878 rec.insert(name.clone(), val);
4879 }
4880 if sender.send(Ok(Value::record_dynamic(rec))).is_err() {
4881 break;
4882 }
4883 }
4884 }
4885 }
4886}
4887
4888fn pg_cursor_producer(
4892 conn_arc: Arc<Mutex<SqlConn>>,
4893 stmt_str: String,
4894 params: Vec<SqlParamValue>,
4895 sender: std::sync::mpsc::SyncSender<Result<Value, String>>,
4896) {
4897 let mut conn_guard = match conn_arc.lock() {
4898 Ok(g) => g,
4899 Err(p) => p.into_inner(),
4900 };
4901 let SqlConn::Postgres(c) = &mut *conn_guard else {
4902 let _ = sender.send(Err("pg_cursor_producer called on non-postgres conn".into()));
4903 return;
4904 };
4905 let pg = pg_param_refs(¶ms);
4906 let refs: Vec<&(dyn postgres::types::ToSql + Sync)> =
4907 pg.iter().map(|b| b.as_ref()).collect();
4908 let mut tx = match c.transaction() {
4909 Ok(t) => t,
4910 Err(e) => { let _ = sender.send(Err(format!("begin: {e}"))); return; }
4911 };
4912 let cur_name = format!("__lex_cur_{}", next_cursor_handle());
4915 if let Err(e) = tx.execute(
4916 &format!("DECLARE \"{cur_name}\" NO SCROLL CURSOR FOR {stmt_str}"),
4917 &refs,
4918 ) {
4919 let _ = sender.send(Err(format!("declare: {e}")));
4920 return;
4921 }
4922 let fetch_sql = format!("FETCH 64 FROM \"{cur_name}\"");
4923 'outer: loop {
4924 let batch = match tx.query(&fetch_sql, &[]) {
4925 Ok(r) => r,
4926 Err(e) => { let _ = sender.send(Err(format!("fetch: {e}"))); break; }
4927 };
4928 if batch.is_empty() {
4929 break;
4930 }
4931 for row in batch.iter() {
4932 let rec = pg_row_to_lex_record(row);
4933 if sender.send(Ok(Value::record_dynamic(rec))).is_err() {
4934 break 'outer;
4935 }
4936 }
4937 }
4938 let _ = tx.execute(&format!("CLOSE \"{cur_name}\""), &[]);
4939 let _ = tx.commit();
4940}
4941
4942#[derive(Debug, Clone)]
4944enum SqlParamValue {
4945 Text(String),
4946 Integer(i64),
4947 Real(f64),
4948 Bool(bool),
4949 Null,
4950}
4951
4952pub(crate) enum SqlConn {
4954 Sqlite(rusqlite::Connection),
4955 Postgres(postgres::Client),
4956}
4957
4958type SharedConn = Arc<Mutex<SqlConn>>;
4959
4960pub(crate) struct SqlRegistry {
4961 entries: indexmap::IndexMap<u64, SharedConn>,
4962 cap: usize,
4963}
4964
4965impl SqlRegistry {
4966 pub(crate) fn with_capacity(cap: usize) -> Self {
4967 Self { entries: indexmap::IndexMap::new(), cap }
4968 }
4969
4970 pub(crate) fn insert(&mut self, handle: u64, conn: SqlConn) {
4971 if self.entries.len() >= self.cap {
4972 self.entries.shift_remove_index(0);
4973 }
4974 self.entries.insert(handle, Arc::new(Mutex::new(conn)));
4975 }
4976
4977 pub(crate) fn touch_get(&mut self, handle: u64) -> Option<SharedConn> {
4981 let idx = self.entries.get_index_of(&handle)?;
4982 self.entries.move_index(idx, self.entries.len() - 1);
4983 self.entries.get(&handle).cloned()
4984 }
4985
4986 pub(crate) fn remove(&mut self, handle: u64) {
4987 self.entries.shift_remove(&handle);
4988 }
4989
4990 #[cfg(test)]
4991 pub(crate) fn len(&self) -> usize { self.entries.len() }
4992}
4993
4994fn next_sql_handle() -> u64 {
4995 static COUNTER: AtomicU64 = AtomicU64::new(1);
4996 COUNTER.fetch_add(1, Ordering::SeqCst)
4997}
4998
4999#[cfg(test)]
5000mod sql_registry_tests {
5001 use super::{SqlConn, SqlRegistry};
5002
5003 fn fresh() -> SqlConn {
5004 SqlConn::Sqlite(rusqlite::Connection::open_in_memory().expect("open in-memory sqlite"))
5005 }
5006
5007 #[test]
5008 fn insert_and_get_round_trip() {
5009 let mut r = SqlRegistry::with_capacity(4);
5010 r.insert(1, fresh());
5011 assert!(r.touch_get(1).is_some());
5012 assert!(r.touch_get(2).is_none());
5013 }
5014
5015 #[test]
5016 fn cap_evicts_lru_on_overflow() {
5017 let mut r = SqlRegistry::with_capacity(2);
5018 r.insert(1, fresh());
5019 r.insert(2, fresh());
5020 let _ = r.touch_get(1);
5021 r.insert(3, fresh());
5022 assert!(r.touch_get(1).is_some(), "1 was MRU, should survive");
5023 assert!(r.touch_get(2).is_none(), "2 was LRU, should be evicted");
5024 assert!(r.touch_get(3).is_some(), "3 just inserted");
5025 assert_eq!(r.len(), 2);
5026 }
5027
5028 #[test]
5029 fn remove_drops_entry() {
5030 let mut r = SqlRegistry::with_capacity(4);
5031 r.insert(1, fresh());
5032 r.remove(1);
5033 assert!(r.touch_get(1).is_none());
5034 assert_eq!(r.len(), 0);
5035 }
5036
5037 #[test]
5038 fn many_inserts_stay_bounded_at_cap() {
5039 let cap = 8;
5040 let mut r = SqlRegistry::with_capacity(cap);
5041 for i in 0..(cap as u64 * 3) {
5042 r.insert(i, fresh());
5043 assert!(r.len() <= cap);
5044 }
5045 assert_eq!(r.len(), cap);
5046 }
5047}
5048
5049#[cfg(test)]
5050mod kv_registry_tests {
5051 use super::KvRegistry;
5052
5053 fn fresh_db(tag: &str) -> sled::Db {
5056 let dir = std::env::temp_dir().join(format!(
5057 "lex-kv-reg-{}-{}-{}",
5058 std::process::id(),
5059 tag,
5060 std::time::SystemTime::now()
5061 .duration_since(std::time::UNIX_EPOCH)
5062 .unwrap()
5063 .as_nanos()
5064 ));
5065 sled::open(&dir).expect("sled open")
5066 }
5067
5068 #[test]
5069 fn insert_and_get_round_trip() {
5070 let mut r = KvRegistry::with_capacity(4);
5071 r.insert(1, fresh_db("a"));
5072 assert!(r.touch_get(1).is_some());
5073 assert!(r.touch_get(2).is_none());
5074 }
5075
5076 #[test]
5077 fn cap_evicts_lru_on_overflow() {
5078 let mut r = KvRegistry::with_capacity(2);
5080 r.insert(1, fresh_db("c1"));
5081 r.insert(2, fresh_db("c2"));
5082 let _ = r.touch_get(1);
5083 r.insert(3, fresh_db("c3"));
5084 assert!(r.touch_get(1).is_some(), "1 was MRU, should survive");
5085 assert!(r.touch_get(2).is_none(), "2 was LRU, should be evicted");
5086 assert!(r.touch_get(3).is_some(), "3 just inserted, should survive");
5087 assert_eq!(r.len(), 2);
5088 }
5089
5090 #[test]
5091 fn cap_with_no_touches_evicts_in_insertion_order() {
5092 let mut r = KvRegistry::with_capacity(2);
5094 r.insert(10, fresh_db("f1"));
5095 r.insert(20, fresh_db("f2"));
5096 r.insert(30, fresh_db("f3"));
5097 assert!(r.touch_get(10).is_none());
5098 assert!(r.touch_get(20).is_some());
5099 assert!(r.touch_get(30).is_some());
5100 }
5101
5102 #[test]
5103 fn remove_drops_entry() {
5104 let mut r = KvRegistry::with_capacity(4);
5105 r.insert(1, fresh_db("r1"));
5106 r.remove(1);
5107 assert!(r.touch_get(1).is_none());
5108 assert_eq!(r.len(), 0);
5109 }
5110
5111 #[test]
5112 fn remove_unknown_handle_is_noop() {
5113 let mut r = KvRegistry::with_capacity(4);
5114 r.insert(1, fresh_db("u1"));
5115 r.remove(999);
5116 assert!(r.touch_get(1).is_some());
5117 }
5118
5119 #[test]
5120 fn many_inserts_stay_bounded_at_cap() {
5121 let cap = 8;
5124 let mut r = KvRegistry::with_capacity(cap);
5125 for i in 0..(cap as u64 * 3) {
5126 r.insert(i, fresh_db(&format!("b{i}")));
5127 assert!(r.len() <= cap);
5128 }
5129 assert_eq!(r.len(), cap);
5130 }
5131}
5132
5133#[cfg(test)]
5140mod unpack_response_tests {
5141 use super::*;
5142 use std::sync::Arc;
5143 use indexmap::IndexMap;
5144 use lex_bytecode::{Const, Op, Program, Value};
5145 use lex_bytecode::program::{Function, ZERO_BODY_HASH};
5146 use lex_bytecode::vm::Vm;
5147
5148 fn build_arena_response_program() -> Arc<Program> {
5153 let constants = vec![
5154 Const::FieldName("status".into()), Const::FieldName("body".into()), Const::Int(200), Const::VariantName("BodyStr".into()), Const::Str("hello".into()), ];
5160 let mut function_names = IndexMap::new();
5161 function_names.insert("handler".to_string(), 0);
5162 Arc::new(Program {
5163 constants,
5164 functions: vec![Function {
5165 name: "handler".into(),
5166 arity: 0,
5167 locals_count: 0,
5168 code: vec![
5169 Op::PushConst(2), Op::PushConst(4), Op::MakeVariant { name_idx: 3, arity: 1 }, Op::AllocArenaRecord { shape_idx: 0, field_count: 2 }, Op::Return,
5174 ],
5175 effects: vec![],
5176 body_hash: ZERO_BODY_HASH,
5177 refinements: vec![],
5178 field_ic_sites: 0,
5179 }],
5180 function_names,
5181 module_aliases: IndexMap::new(),
5182 entry: Some(0),
5183 record_shapes: vec![vec![0, 1]], })
5185 }
5186
5187 #[test]
5193 fn unpack_response_reads_arena_record_via_slab() {
5194 let p = build_arena_response_program();
5195 let mut vm = Vm::new(&p);
5196 let scope = vm.enter_request_scope();
5197
5198 let resp = vm.invoke(p.function_names["handler"], vec![]).unwrap();
5199 assert!(matches!(resp, Value::ArenaRecord { .. }),
5202 "expected ArenaRecord (slab path), got {resp:?}");
5203
5204 let (status, body, headers) = unpack_response(&mut vm, &resp);
5205 vm.exit_request_scope(scope);
5206
5207 assert_eq!(status, 200);
5208 assert!(headers.is_empty());
5209 match body {
5210 ResponseBodyOut::Str(s) => assert_eq!(s, "hello"),
5211 _ => panic!("expected BodyStr"),
5212 }
5213 }
5214
5215 #[test]
5220 fn unpack_response_reads_heap_record() {
5221 let p = build_arena_response_program();
5222 let mut vm = Vm::new(&p);
5223
5224 let resp = vm.invoke(p.function_names["handler"], vec![]).unwrap();
5226 assert!(matches!(resp, Value::Record { .. }),
5227 "expected heap Record (fallback path), got {resp:?}");
5228
5229 let (status, body, headers) = unpack_response(&mut vm, &resp);
5230 assert_eq!(status, 200);
5231 assert!(headers.is_empty());
5232 match body {
5233 ResponseBodyOut::Str(s) => assert_eq!(s, "hello"),
5234 _ => panic!("expected BodyStr"),
5235 }
5236 }
5237
5238 #[test]
5241 fn unpack_response_falls_back_to_500_on_non_record() {
5242 let p = build_arena_response_program();
5243 let mut vm = Vm::new(&p);
5244 let v = Value::Int(7);
5245 let (status, _body, _headers) = unpack_response(&mut vm, &v);
5246 assert_eq!(status, 500);
5247 }
5248}