feat: add schema evolution type system integration and HTTP server effect

Schema Evolution:
- Preserve version info in type resolution (Type::Versioned)
- Track versioned type declarations in typechecker
- Detect version mismatches at compile time (@v1 vs @v2 errors)
- Support @v2+ (at least) and @latest version constraints
- Store migrations for future auto-migration support
- Fix let bindings to preserve declared type annotations

HTTP Server Effect:
- Add HttpServer effect with listen, accept, respond, respondWithHeaders, stop
- Implement blocking request handling via tiny_http
- Request record includes method, path, body, headers
- Add http_server.lux example with routing via pattern matching
- Add type-checking test for HttpServer effect

Tests: 222 passing (up from 217)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 22:06:31 -05:00
parent 554a1e7c3e
commit 086552b7a4
9 changed files with 1153 additions and 21 deletions

View File

@@ -9,6 +9,7 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
/// Built-in function identifier
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -540,6 +541,10 @@ pub struct Interpreter {
builtin_reader: RefCell<Value>,
/// Depth of handler context (> 0 means we're inside a handler body where resume is valid)
in_handler_depth: usize,
/// HTTP server state (using Arc<Mutex> for thread-safety with tiny_http)
http_server: Arc<Mutex<Option<tiny_http::Server>>>,
/// Current HTTP request being handled (stored for respond operation)
current_http_request: Arc<Mutex<Option<tiny_http::Request>>>,
}
impl Interpreter {
@@ -560,6 +565,8 @@ impl Interpreter {
builtin_state: RefCell::new(Value::Unit),
builtin_reader: RefCell::new(Value::Unit),
in_handler_depth: 0,
http_server: Arc::new(Mutex::new(None)),
current_http_request: Arc::new(Mutex::new(None)),
}
}
@@ -3301,6 +3308,164 @@ impl Interpreter {
Ok(Value::Unit)
}
// ===== HttpServer Effect =====
("HttpServer", "listen") => {
let port = match request.args.first() {
Some(Value::Int(p)) => *p as u16,
_ => return Err(RuntimeError {
message: "HttpServer.listen requires an integer port".to_string(),
span: None,
}),
};
let addr = format!("0.0.0.0:{}", port);
match tiny_http::Server::http(&addr) {
Ok(server) => {
*self.http_server.lock().unwrap() = Some(server);
Ok(Value::Unit)
}
Err(e) => Err(RuntimeError {
message: format!("Failed to start HTTP server on port {}: {}", port, e),
span: None,
}),
}
}
("HttpServer", "accept") => {
let server_guard = self.http_server.lock().unwrap();
let server = match server_guard.as_ref() {
Some(s) => s,
None => return Err(RuntimeError {
message: "HttpServer.accept: No server is listening. Call HttpServer.listen first.".to_string(),
span: None,
}),
};
// Block until a request arrives
match server.recv() {
Ok(mut request) => {
// Extract request info
let method = request.method().to_string();
let path = request.url().to_string();
// Read body
let mut body = String::new();
{
use std::io::Read;
let reader = request.as_reader();
let _ = reader.read_to_string(&mut body);
}
// Extract headers
let headers: Vec<Value> = request.headers()
.iter()
.map(|h| Value::Tuple(vec![
Value::String(h.field.as_str().to_string()),
Value::String(h.value.as_str().to_string()),
]))
.collect();
// Store the request for respond operation
drop(server_guard); // Release lock before storing request
*self.current_http_request.lock().unwrap() = Some(request);
// Return request as a record
Ok(Value::Record(HashMap::from([
("method".to_string(), Value::String(method)),
("path".to_string(), Value::String(path)),
("body".to_string(), Value::String(body)),
("headers".to_string(), Value::List(headers)),
])))
}
Err(e) => Err(RuntimeError {
message: format!("HttpServer.accept failed: {}", e),
span: None,
}),
}
}
("HttpServer", "respond") => {
let (status, body) = match (request.args.get(0), request.args.get(1)) {
(Some(Value::Int(s)), Some(Value::String(b))) => (*s as u16, b.clone()),
_ => return Err(RuntimeError {
message: "HttpServer.respond requires status (Int) and body (String)".to_string(),
span: None,
}),
};
let mut req_guard = self.current_http_request.lock().unwrap();
match req_guard.take() {
Some(http_request) => {
let status_code = tiny_http::StatusCode(status);
let response = tiny_http::Response::from_string(body)
.with_status_code(status_code);
if let Err(e) = http_request.respond(response) {
return Err(RuntimeError {
message: format!("Failed to send HTTP response: {}", e),
span: None,
});
}
Ok(Value::Unit)
}
None => Err(RuntimeError {
message: "HttpServer.respond: No pending request to respond to".to_string(),
span: None,
}),
}
}
("HttpServer", "respondWithHeaders") => {
let (status, body, headers) = match (request.args.get(0), request.args.get(1), request.args.get(2)) {
(Some(Value::Int(s)), Some(Value::String(b)), Some(Value::List(h))) => {
(*s as u16, b.clone(), h.clone())
}
_ => return Err(RuntimeError {
message: "HttpServer.respondWithHeaders requires status (Int), body (String), and headers (List)".to_string(),
span: None,
}),
};
let mut req_guard = self.current_http_request.lock().unwrap();
match req_guard.take() {
Some(http_request) => {
let status_code = tiny_http::StatusCode(status);
let mut response = tiny_http::Response::from_string(body)
.with_status_code(status_code);
// Add custom headers
for header_val in headers {
if let Value::Tuple(pair) = header_val {
if let (Some(Value::String(name)), Some(Value::String(value))) =
(pair.get(0), pair.get(1))
{
if let Ok(header) = tiny_http::Header::from_bytes(
name.as_bytes(),
value.as_bytes(),
) {
response = response.with_header(header);
}
}
}
}
if let Err(e) = http_request.respond(response) {
return Err(RuntimeError {
message: format!("Failed to send HTTP response: {}", e),
span: None,
});
}
Ok(Value::Unit)
}
None => Err(RuntimeError {
message: "HttpServer.respondWithHeaders: No pending request to respond to".to_string(),
span: None,
}),
}
}
("HttpServer", "stop") => {
// Drop the server to stop listening
*self.http_server.lock().unwrap() = None;
*self.current_http_request.lock().unwrap() = None;
Ok(Value::Unit)
}
_ => Err(RuntimeError {
message: format!(
"Unhandled effect operation: {}.{}",