haystack_server/ops/
invoke.rs1use axum::extract::State;
4use axum::http::HeaderMap;
5use axum::response::{IntoResponse, Response};
6
7use haystack_core::kinds::Kind;
8
9use crate::content;
10use crate::error::HaystackError;
11use crate::state::SharedState;
12
13pub async fn handle(
15 State(state): State<SharedState>,
16 headers: HeaderMap,
17 body: String,
18) -> Result<Response, HaystackError> {
19 let content_type = headers
20 .get("Content-Type")
21 .and_then(|v| v.to_str().ok())
22 .unwrap_or("");
23 let accept = headers
24 .get("Accept")
25 .and_then(|v| v.to_str().ok())
26 .unwrap_or("");
27
28 let request_grid = content::decode_request_grid(&body, content_type)
29 .map_err(|e| HaystackError::bad_request(format!("failed to decode request: {e}")))?;
30
31 let row = request_grid
32 .row(0)
33 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
34
35 let ref_val = match row.get("id") {
36 Some(Kind::Ref(r)) => &r.val,
37 _ => {
38 return Err(HaystackError::bad_request(
39 "request row must have an 'id' column with a Ref value",
40 ));
41 }
42 };
43
44 let action = match row.get("action") {
45 Some(Kind::Str(s)) => s.as_str(),
46 _ => {
47 return Err(HaystackError::bad_request(
48 "request row must have an 'action' column with a Str value",
49 ));
50 }
51 };
52
53 let entity = state
55 .graph
56 .get(ref_val)
57 .ok_or_else(|| HaystackError::not_found(format!("entity not found: {ref_val}")))?;
58
59 let args = row.clone();
61
62 log::info!("invokeAction: id={ref_val} action={action}");
63
64 let result_grid = state
66 .actions
67 .invoke(&entity, action, &args)
68 .map_err(HaystackError::bad_request)?;
69
70 let (encoded, ct) = content::encode_response_grid(&result_grid, accept)
71 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
72
73 Ok(([(axum::http::header::CONTENT_TYPE, ct)], encoded).into_response())
74}