1use actix_web::{HttpRequest, HttpResponse, web};
4
5use haystack_core::data::{HCol, HDict, HGrid};
6use haystack_core::kinds::Kind;
7
8use crate::content;
9use crate::error::HaystackError;
10use crate::state::AppState;
11
12pub async fn handle_specs(
14 req: HttpRequest,
15 body: String,
16 state: web::Data<AppState>,
17) -> Result<HttpResponse, HaystackError> {
18 let content_type = req
19 .headers()
20 .get("Content-Type")
21 .and_then(|v| v.to_str().ok())
22 .unwrap_or("");
23 let accept = req
24 .headers()
25 .get("Accept")
26 .and_then(|v| v.to_str().ok())
27 .unwrap_or("");
28
29 let ns = state.namespace.read();
30
31 let lib_filter: Option<String> = if body.trim().is_empty() {
33 None
34 } else {
35 let grid = content::decode_request_grid(&body, content_type)
36 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
37 grid.row(0).and_then(|row| match row.get("lib") {
38 Some(Kind::Str(s)) if !s.is_empty() => Some(s.clone()),
39 _ => None,
40 })
41 };
42
43 let specs = ns.specs(lib_filter.as_deref());
44 let cols = vec![
45 HCol::new("qname"),
46 HCol::new("name"),
47 HCol::new("lib"),
48 HCol::new("base"),
49 HCol::new("doc"),
50 HCol::new("abstract"),
51 ];
52
53 let mut rows: Vec<HDict> = specs
54 .iter()
55 .map(|spec| {
56 let mut row = HDict::new();
57 row.set("qname", Kind::Str(spec.qname.clone()));
58 row.set("name", Kind::Str(spec.name.clone()));
59 row.set("lib", Kind::Str(spec.lib.clone()));
60 if let Some(ref base) = spec.base {
61 row.set("base", Kind::Str(base.clone()));
62 }
63 row.set("doc", Kind::Str(spec.doc.clone()));
64 if spec.is_abstract {
65 row.set("abstract", Kind::Marker);
66 }
67 row
68 })
69 .collect();
70
71 rows.sort_by(|a, b| {
72 let a_name = match a.get("qname") {
73 Some(Kind::Str(s)) => s.as_str(),
74 _ => "",
75 };
76 let b_name = match b.get("qname") {
77 Some(Kind::Str(s)) => s.as_str(),
78 _ => "",
79 };
80 a_name.cmp(b_name)
81 });
82
83 let grid = HGrid::from_parts(HDict::new(), cols, rows);
84 let (encoded, ct) = content::encode_response_grid(&grid, accept)
85 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
86 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
87}
88
89pub async fn handle_spec(
91 req: HttpRequest,
92 body: String,
93 state: web::Data<AppState>,
94) -> Result<HttpResponse, HaystackError> {
95 let content_type = req
96 .headers()
97 .get("Content-Type")
98 .and_then(|v| v.to_str().ok())
99 .unwrap_or("");
100 let accept = req
101 .headers()
102 .get("Accept")
103 .and_then(|v| v.to_str().ok())
104 .unwrap_or("");
105
106 let grid = content::decode_request_grid(&body, content_type)
107 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
108 let row = grid
109 .row(0)
110 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
111 let qname = match row.get("qname") {
112 Some(Kind::Str(s)) => s.clone(),
113 _ => return Err(HaystackError::bad_request("qname column required")),
114 };
115
116 let ns = state.namespace.read();
117 let spec = ns
118 .get_spec(&qname)
119 .ok_or_else(|| HaystackError::bad_request(format!("spec '{}' not found", qname)))?;
120
121 let cols = vec![
122 HCol::new("qname"),
123 HCol::new("name"),
124 HCol::new("lib"),
125 HCol::new("base"),
126 HCol::new("doc"),
127 HCol::new("abstract"),
128 HCol::new("slots"),
129 ];
130
131 let mut result = HDict::new();
132 result.set("qname", Kind::Str(spec.qname.clone()));
133 result.set("name", Kind::Str(spec.name.clone()));
134 result.set("lib", Kind::Str(spec.lib.clone()));
135 if let Some(ref base) = spec.base {
136 result.set("base", Kind::Str(base.clone()));
137 }
138 result.set("doc", Kind::Str(spec.doc.clone()));
139 if spec.is_abstract {
140 result.set("abstract", Kind::Marker);
141 }
142 let slot_names: Vec<String> = spec.slots.iter().map(|s| s.name.clone()).collect();
144 result.set("slots", Kind::Str(slot_names.join(",")));
145
146 let grid = HGrid::from_parts(HDict::new(), cols, vec![result]);
147 let (encoded, ct) = content::encode_response_grid(&grid, accept)
148 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
149 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
150}
151
152pub async fn handle_load_lib(
154 req: HttpRequest,
155 body: String,
156 state: web::Data<AppState>,
157) -> Result<HttpResponse, HaystackError> {
158 let content_type = req
159 .headers()
160 .get("Content-Type")
161 .and_then(|v| v.to_str().ok())
162 .unwrap_or("");
163 let accept = req
164 .headers()
165 .get("Accept")
166 .and_then(|v| v.to_str().ok())
167 .unwrap_or("");
168
169 let grid = content::decode_request_grid(&body, content_type)
170 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
171 let row = grid
172 .row(0)
173 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
174 let name = match row.get("name") {
175 Some(Kind::Str(s)) => s.clone(),
176 _ => return Err(HaystackError::bad_request("name column required")),
177 };
178 let source = match row.get("source") {
179 Some(Kind::Str(s)) => s.clone(),
180 _ => return Err(HaystackError::bad_request("source column required")),
181 };
182
183 let mut ns = state.namespace.write();
184 let qnames = ns
185 .load_xeto_str(&source, &name)
186 .map_err(|e| HaystackError::bad_request(format!("load error: {e}")))?;
187
188 let cols = vec![HCol::new("loaded"), HCol::new("specs")];
189 let mut result = HDict::new();
190 result.set("loaded", Kind::Str(name));
191 result.set("specs", Kind::Str(qnames.join(",")));
192 let grid = HGrid::from_parts(HDict::new(), cols, vec![result]);
193 let (encoded, ct) = content::encode_response_grid(&grid, accept)
194 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
195 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
196}
197
198pub async fn handle_unload_lib(
200 req: HttpRequest,
201 body: String,
202 state: web::Data<AppState>,
203) -> Result<HttpResponse, HaystackError> {
204 let content_type = req
205 .headers()
206 .get("Content-Type")
207 .and_then(|v| v.to_str().ok())
208 .unwrap_or("");
209 let accept = req
210 .headers()
211 .get("Accept")
212 .and_then(|v| v.to_str().ok())
213 .unwrap_or("");
214
215 let grid = content::decode_request_grid(&body, content_type)
216 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
217 let row = grid
218 .row(0)
219 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
220 let name = match row.get("name") {
221 Some(Kind::Str(s)) => s.clone(),
222 _ => return Err(HaystackError::bad_request("name column required")),
223 };
224
225 let mut ns = state.namespace.write();
226 ns.unload_lib(&name).map_err(HaystackError::bad_request)?;
227
228 let cols = vec![HCol::new("unloaded")];
229 let mut result = HDict::new();
230 result.set("unloaded", Kind::Str(name));
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_export_lib(
239 req: HttpRequest,
240 body: String,
241 state: web::Data<AppState>,
242) -> Result<HttpResponse, HaystackError> {
243 let content_type = req
244 .headers()
245 .get("Content-Type")
246 .and_then(|v| v.to_str().ok())
247 .unwrap_or("");
248 let accept = req
249 .headers()
250 .get("Accept")
251 .and_then(|v| v.to_str().ok())
252 .unwrap_or("");
253
254 let grid = content::decode_request_grid(&body, content_type)
255 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
256 let row = grid
257 .row(0)
258 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
259 let name = match row.get("name") {
260 Some(Kind::Str(s)) => s.clone(),
261 _ => return Err(HaystackError::bad_request("name column required")),
262 };
263
264 let ns = state.namespace.read();
265 let xeto_text = ns
266 .export_lib_xeto(&name)
267 .map_err(HaystackError::bad_request)?;
268
269 let cols = vec![HCol::new("name"), HCol::new("source")];
270 let mut result = HDict::new();
271 result.set("name", Kind::Str(name));
272 result.set("source", Kind::Str(xeto_text));
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_validate(
281 req: HttpRequest,
282 body: String,
283 state: web::Data<AppState>,
284) -> Result<HttpResponse, HaystackError> {
285 let content_type = req
286 .headers()
287 .get("Content-Type")
288 .and_then(|v| v.to_str().ok())
289 .unwrap_or("");
290 let accept = req
291 .headers()
292 .get("Accept")
293 .and_then(|v| v.to_str().ok())
294 .unwrap_or("");
295
296 let grid = content::decode_request_grid(&body, content_type)
297 .map_err(|e| HaystackError::bad_request(format!("decode error: {e}")))?;
298
299 let ns = state.namespace.read();
300
301 let cols = vec![
302 HCol::new("entity"),
303 HCol::new("issueType"),
304 HCol::new("detail"),
305 ];
306 let mut rows: Vec<HDict> = Vec::new();
307
308 for entity in &grid.rows {
309 let issues = ns.validate_entity(entity);
310 for issue in issues {
311 let mut row = HDict::new();
312 if let Some(ref e) = issue.entity {
313 row.set("entity", Kind::Str(e.clone()));
314 }
315 row.set("issueType", Kind::Str(issue.issue_type));
316 row.set("detail", Kind::Str(issue.detail));
317 rows.push(row);
318 }
319 }
320
321 let grid = HGrid::from_parts(HDict::new(), cols, rows);
322 let (encoded, ct) = content::encode_response_grid(&grid, accept)
323 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
324 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
325}