1use actix_web::{HttpRequest, HttpResponse, web};
31
32use haystack_core::data::{HCol, HDict, HGrid};
33use haystack_core::kinds::Kind;
34
35use crate::content;
36use crate::error::HaystackError;
37use crate::state::AppState;
38
39pub async fn handle_specs(
45 req: HttpRequest,
46 body: String,
47 state: web::Data<AppState>,
48) -> Result<HttpResponse, HaystackError> {
49 let content_type = req
50 .headers()
51 .get("Content-Type")
52 .and_then(|v| v.to_str().ok())
53 .unwrap_or("");
54 let accept = req
55 .headers()
56 .get("Accept")
57 .and_then(|v| v.to_str().ok())
58 .unwrap_or("");
59
60 let ns = state.namespace.read();
61
62 let lib_filter: Option<String> = if body.trim().is_empty() {
64 None
65 } else {
66 let grid = content::decode_request_grid(&body, content_type)
67 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
68 grid.row(0).and_then(|row| match row.get("lib") {
69 Some(Kind::Str(s)) if !s.is_empty() => Some(s.clone()),
70 _ => None,
71 })
72 };
73
74 let specs = ns.specs(lib_filter.as_deref());
75 let cols = vec![
76 HCol::new("qname"),
77 HCol::new("name"),
78 HCol::new("lib"),
79 HCol::new("base"),
80 HCol::new("doc"),
81 HCol::new("abstract"),
82 ];
83
84 let mut rows: Vec<HDict> = specs
85 .iter()
86 .map(|spec| {
87 let mut row = HDict::new();
88 row.set("qname", Kind::Str(spec.qname.clone()));
89 row.set("name", Kind::Str(spec.name.clone()));
90 row.set("lib", Kind::Str(spec.lib.clone()));
91 if let Some(ref base) = spec.base {
92 row.set("base", Kind::Str(base.clone()));
93 }
94 row.set("doc", Kind::Str(spec.doc.clone()));
95 if spec.is_abstract {
96 row.set("abstract", Kind::Marker);
97 }
98 row
99 })
100 .collect();
101
102 rows.sort_by(|a, b| {
103 let a_name = match a.get("qname") {
104 Some(Kind::Str(s)) => s.as_str(),
105 _ => "",
106 };
107 let b_name = match b.get("qname") {
108 Some(Kind::Str(s)) => s.as_str(),
109 _ => "",
110 };
111 a_name.cmp(b_name)
112 });
113
114 let grid = HGrid::from_parts(HDict::new(), cols, rows);
115 let (encoded, ct) = content::encode_response_grid(&grid, accept)
116 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
117 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
118}
119
120pub async fn handle_spec(
126 req: HttpRequest,
127 body: String,
128 state: web::Data<AppState>,
129) -> Result<HttpResponse, HaystackError> {
130 let content_type = req
131 .headers()
132 .get("Content-Type")
133 .and_then(|v| v.to_str().ok())
134 .unwrap_or("");
135 let accept = req
136 .headers()
137 .get("Accept")
138 .and_then(|v| v.to_str().ok())
139 .unwrap_or("");
140
141 let grid = content::decode_request_grid(&body, content_type)
142 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
143 let row = grid
144 .row(0)
145 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
146 let qname = match row.get("qname") {
147 Some(Kind::Str(s)) => s.clone(),
148 _ => return Err(HaystackError::bad_request("qname column required")),
149 };
150
151 let ns = state.namespace.read();
152 let spec = ns
153 .get_spec(&qname)
154 .ok_or_else(|| HaystackError::bad_request(format!("spec '{}' not found", qname)))?;
155
156 let cols = vec![
157 HCol::new("qname"),
158 HCol::new("name"),
159 HCol::new("lib"),
160 HCol::new("base"),
161 HCol::new("doc"),
162 HCol::new("abstract"),
163 HCol::new("slots"),
164 ];
165
166 let mut result = HDict::new();
167 result.set("qname", Kind::Str(spec.qname.clone()));
168 result.set("name", Kind::Str(spec.name.clone()));
169 result.set("lib", Kind::Str(spec.lib.clone()));
170 if let Some(ref base) = spec.base {
171 result.set("base", Kind::Str(base.clone()));
172 }
173 result.set("doc", Kind::Str(spec.doc.clone()));
174 if spec.is_abstract {
175 result.set("abstract", Kind::Marker);
176 }
177 let slot_names: Vec<String> = spec.slots.iter().map(|s| s.name.clone()).collect();
179 result.set("slots", Kind::Str(slot_names.join(",")));
180
181 let grid = HGrid::from_parts(HDict::new(), cols, vec![result]);
182 let (encoded, ct) = content::encode_response_grid(&grid, accept)
183 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
184 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
185}
186
187pub async fn handle_load_lib(
193 req: HttpRequest,
194 body: String,
195 state: web::Data<AppState>,
196) -> Result<HttpResponse, HaystackError> {
197 let content_type = req
198 .headers()
199 .get("Content-Type")
200 .and_then(|v| v.to_str().ok())
201 .unwrap_or("");
202 let accept = req
203 .headers()
204 .get("Accept")
205 .and_then(|v| v.to_str().ok())
206 .unwrap_or("");
207
208 let grid = content::decode_request_grid(&body, content_type)
209 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
210 let row = grid
211 .row(0)
212 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
213 let name = match row.get("name") {
214 Some(Kind::Str(s)) => s.clone(),
215 _ => return Err(HaystackError::bad_request("name column required")),
216 };
217 let source = match row.get("source") {
218 Some(Kind::Str(s)) => s.clone(),
219 _ => return Err(HaystackError::bad_request("source column required")),
220 };
221
222 let mut ns = state.namespace.write();
223 let qnames = ns
224 .load_xeto_str(&source, &name)
225 .map_err(|e| HaystackError::bad_request(format!("load error: {e}")))?;
226
227 let cols = vec![HCol::new("loaded"), HCol::new("specs")];
228 let mut result = HDict::new();
229 result.set("loaded", Kind::Str(name));
230 result.set("specs", Kind::Str(qnames.join(",")));
231 let grid = HGrid::from_parts(HDict::new(), cols, vec![result]);
232 let (encoded, ct) = content::encode_response_grid(&grid, accept)
233 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
234 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
235}
236
237pub async fn handle_unload_lib(
242 req: HttpRequest,
243 body: String,
244 state: web::Data<AppState>,
245) -> Result<HttpResponse, HaystackError> {
246 let content_type = req
247 .headers()
248 .get("Content-Type")
249 .and_then(|v| v.to_str().ok())
250 .unwrap_or("");
251 let accept = req
252 .headers()
253 .get("Accept")
254 .and_then(|v| v.to_str().ok())
255 .unwrap_or("");
256
257 let grid = content::decode_request_grid(&body, content_type)
258 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
259 let row = grid
260 .row(0)
261 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
262 let name = match row.get("name") {
263 Some(Kind::Str(s)) => s.clone(),
264 _ => return Err(HaystackError::bad_request("name column required")),
265 };
266
267 let mut ns = state.namespace.write();
268 ns.unload_lib(&name).map_err(HaystackError::bad_request)?;
269
270 let cols = vec![HCol::new("unloaded")];
271 let mut result = HDict::new();
272 result.set("unloaded", Kind::Str(name));
273 let grid = HGrid::from_parts(HDict::new(), cols, vec![result]);
274 let (encoded, ct) = content::encode_response_grid(&grid, accept)
275 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
276 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
277}
278
279pub async fn handle_export_lib(
284 req: HttpRequest,
285 body: String,
286 state: web::Data<AppState>,
287) -> Result<HttpResponse, HaystackError> {
288 let content_type = req
289 .headers()
290 .get("Content-Type")
291 .and_then(|v| v.to_str().ok())
292 .unwrap_or("");
293 let accept = req
294 .headers()
295 .get("Accept")
296 .and_then(|v| v.to_str().ok())
297 .unwrap_or("");
298
299 let grid = content::decode_request_grid(&body, content_type)
300 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
301 let row = grid
302 .row(0)
303 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
304 let name = match row.get("name") {
305 Some(Kind::Str(s)) => s.clone(),
306 _ => return Err(HaystackError::bad_request("name column required")),
307 };
308
309 let ns = state.namespace.read();
310 let xeto_text = ns
311 .export_lib_xeto(&name)
312 .map_err(HaystackError::bad_request)?;
313
314 let cols = vec![HCol::new("name"), HCol::new("source")];
315 let mut result = HDict::new();
316 result.set("name", Kind::Str(name));
317 result.set("source", Kind::Str(xeto_text));
318 let grid = HGrid::from_parts(HDict::new(), cols, vec![result]);
319 let (encoded, ct) = content::encode_response_grid(&grid, accept)
320 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
321 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
322}
323
324pub async fn handle_validate(
331 req: HttpRequest,
332 body: String,
333 state: web::Data<AppState>,
334) -> Result<HttpResponse, HaystackError> {
335 let content_type = req
336 .headers()
337 .get("Content-Type")
338 .and_then(|v| v.to_str().ok())
339 .unwrap_or("");
340 let accept = req
341 .headers()
342 .get("Accept")
343 .and_then(|v| v.to_str().ok())
344 .unwrap_or("");
345
346 let grid = content::decode_request_grid(&body, content_type)
347 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
348
349 let ns = state.namespace.read();
350
351 let cols = vec![
352 HCol::new("entity"),
353 HCol::new("issueType"),
354 HCol::new("detail"),
355 ];
356 let mut rows: Vec<HDict> = Vec::new();
357
358 for entity in &grid.rows {
359 let issues = ns.validate_entity(entity);
360 for issue in issues {
361 let mut row = HDict::new();
362 if let Some(ref e) = issue.entity {
363 row.set("entity", Kind::Str(e.clone()));
364 }
365 row.set("issueType", Kind::Str(issue.issue_type));
366 row.set("detail", Kind::Str(issue.detail));
367 rows.push(row);
368 }
369 }
370
371 let grid = HGrid::from_parts(HDict::new(), cols, rows);
372 let (encoded, ct) = content::encode_response_grid(&grid, accept)
373 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
374 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
375}