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