1use intent_ir::Module;
2use serde::Serialize;
3use serde_json::json;
4use tiny_http::{Header, Method, Response, Server};
5
6use crate::contract::{ActionRequest, execute_action};
7
8pub fn serve(module: Module, addr: &str) -> Result<(), Box<dyn std::error::Error>> {
14 let server = Server::http(addr).map_err(|e| format!("failed to bind {addr}: {e}"))?;
15 eprintln!("intent serve: listening on http://{addr}");
16 eprintln!(" module: {}", module.name);
17 for func in &module.functions {
18 eprintln!(" POST /actions/{}", func.name);
19 }
20 eprintln!(" GET /");
21
22 for mut request in server.incoming_requests() {
23 let url = request.url().to_string();
24 let method = request.method().clone();
25
26 let (status, body) = match (method, url.as_str()) {
27 (Method::Get, "/") => {
28 let info = module_info(&module);
29 (200, serde_json::to_string_pretty(&info).unwrap())
30 }
31 (Method::Post, path) if path.starts_with("/actions/") => {
32 let action_name = &path["/actions/".len()..];
33 handle_action(&module, action_name, &mut request)
34 }
35 _ => (404, json!({"error": "not found"}).to_string()),
36 };
37
38 let content_type = Header::from_bytes("Content-Type", "application/json").unwrap();
39 let response = Response::from_string(body)
40 .with_status_code(status)
41 .with_header(content_type);
42 request.respond(response).ok();
43 }
44 Ok(())
45}
46
47fn handle_action(
48 module: &Module,
49 action_name: &str,
50 request: &mut tiny_http::Request,
51) -> (i32, String) {
52 let mut body = String::new();
53 if request.as_reader().read_to_string(&mut body).is_err() {
54 return (
55 400,
56 json!({"error": "failed to read request body"}).to_string(),
57 );
58 }
59
60 let action_request: ActionRequest = match serde_json::from_str::<ActionRequest>(&body) {
61 Ok(mut req) => {
62 req.action = action_name.to_string();
63 req
64 }
65 Err(e) => {
66 return (
67 400,
68 json!({"error": format!("invalid JSON: {e}")}).to_string(),
69 );
70 }
71 };
72
73 match execute_action(module, &action_request) {
74 Ok(result) => {
75 let status = if result.ok { 200 } else { 422 };
76 (status, serde_json::to_string_pretty(&result).unwrap())
77 }
78 Err(e) => (400, json!({"error": format!("{e}")}).to_string()),
79 }
80}
81
82#[derive(Serialize)]
83struct ModuleInfo {
84 name: String,
85 entities: Vec<EntityInfo>,
86 actions: Vec<ActionInfo>,
87 invariants: Vec<String>,
88}
89
90#[derive(Serialize)]
91struct EntityInfo {
92 name: String,
93 fields: Vec<FieldInfo>,
94}
95
96#[derive(Serialize)]
97struct FieldInfo {
98 name: String,
99 #[serde(rename = "type")]
100 ty: String,
101}
102
103#[derive(Serialize)]
104struct ActionInfo {
105 name: String,
106 params: Vec<FieldInfo>,
107 precondition_count: usize,
108 postcondition_count: usize,
109}
110
111fn module_info(module: &Module) -> ModuleInfo {
112 ModuleInfo {
113 name: module.name.clone(),
114 entities: module
115 .structs
116 .iter()
117 .map(|s| EntityInfo {
118 name: s.name.clone(),
119 fields: s
120 .fields
121 .iter()
122 .map(|f| FieldInfo {
123 name: f.name.clone(),
124 ty: format!("{:?}", f.ty),
125 })
126 .collect(),
127 })
128 .collect(),
129 actions: module
130 .functions
131 .iter()
132 .map(|f| ActionInfo {
133 name: f.name.clone(),
134 params: f
135 .params
136 .iter()
137 .map(|p| FieldInfo {
138 name: p.name.clone(),
139 ty: format!("{:?}", p.ty),
140 })
141 .collect(),
142 precondition_count: f.preconditions.len(),
143 postcondition_count: f.postconditions.len(),
144 })
145 .collect(),
146 invariants: module.invariants.iter().map(|i| i.name.clone()).collect(),
147 }
148}