haystack_server/ops/
read.rs1use axum::extract::State;
4use axum::http::HeaderMap;
5use axum::response::{IntoResponse, Response};
6
7use haystack_core::data::{HCol, HDict, HGrid};
8use haystack_core::kinds::{HRef, Kind};
9use std::sync::Arc;
10
11use crate::content;
12use crate::error::HaystackError;
13use crate::state::SharedState;
14
15fn haystack_response(body: Vec<u8>, content_type: &str) -> Response {
17 (
18 [(axum::http::header::CONTENT_TYPE, content_type.to_string())],
19 body,
20 )
21 .into_response()
22}
23
24pub async fn handle(
26 State(state): State<SharedState>,
27 headers: HeaderMap,
28 body: String,
29) -> Result<Response, HaystackError> {
30 let content_type = headers
31 .get("Content-Type")
32 .and_then(|v| v.to_str().ok())
33 .unwrap_or("");
34 let accept = headers
35 .get("Accept")
36 .and_then(|v| v.to_str().ok())
37 .unwrap_or("");
38
39 let request_grid = content::decode_request_grid(&body, content_type)
40 .map_err(|e| HaystackError::bad_request(format!("failed to decode request: {e}")))?;
41
42 let result_grid = if request_grid.col("id").is_some() {
43 read_by_id(&request_grid, &state)
44 } else if request_grid.col("filter").is_some() {
45 read_by_filter(&request_grid, &state)
46 } else {
47 Err(HaystackError::bad_request(
48 "request must have 'id' or 'filter' column",
49 ))
50 }?;
51
52 let (encoded, ct) = content::encode_response_grid(&result_grid, accept)
53 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
54
55 Ok(haystack_response(encoded, ct))
56}
57
58fn read_by_filter(request_grid: &HGrid, state: &SharedState) -> Result<HGrid, HaystackError> {
60 let row = request_grid
61 .row(0)
62 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
63
64 let filter = match row.get("filter") {
65 Some(Kind::Str(s)) => s.as_str(),
66 _ => return Err(HaystackError::bad_request("filter must be a Str value")),
67 };
68
69 let limit = match row.get("limit") {
70 Some(Kind::Number(n)) => n.val as usize,
71 _ => 0, };
73
74 let effective_limit = if limit == 0 { usize::MAX } else { limit };
75 let is_wildcard = filter == "*";
76
77 let mut results: Vec<Arc<HDict>> = if is_wildcard {
78 state
79 .graph
80 .all_entities()
81 .into_iter()
82 .map(Arc::new)
83 .collect()
84 } else {
85 let local_grid = state
86 .graph
87 .read_filter(filter, limit)
88 .map_err(|e| HaystackError::bad_request(format!("filter error: {e}")))?;
89 local_grid.rows.into_iter().map(Arc::new).collect()
90 };
91
92 if results.len() > effective_limit {
93 results.truncate(effective_limit);
94 }
95
96 if results.is_empty() {
97 return Ok(HGrid::new());
98 }
99
100 let mut col_set: Vec<&str> = Vec::new();
102 let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
103 for entity in &results {
104 for name in entity.tag_names() {
105 if seen.insert(name) {
106 col_set.push(name);
107 }
108 }
109 }
110 col_set.sort_unstable();
111 let cols: Vec<HCol> = col_set.iter().map(|&n| HCol::new(n)).collect();
112
113 Ok(HGrid::from_parts_arc(HDict::new(), cols, results))
114}
115
116fn read_by_id(request_grid: &HGrid, state: &SharedState) -> Result<HGrid, HaystackError> {
118 let mut results: Vec<Arc<HDict>> = Vec::new();
119
120 for row in request_grid.rows.iter() {
121 let ref_val = match row.get("id") {
122 Some(Kind::Ref(r)) => &r.val,
123 _ => continue,
124 };
125
126 if let Some(entity) = state.graph.get(ref_val) {
127 results.push(Arc::new(entity));
128 } else {
129 let mut missing = HDict::new();
131 missing.set("id", Kind::Ref(HRef::from_val(ref_val.as_str())));
132 results.push(Arc::new(missing));
133 }
134 }
135
136 if results.is_empty() {
137 return Ok(HGrid::new());
138 }
139
140 let mut col_set: Vec<&str> = Vec::new();
142 let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
143 for entity in &results {
144 for name in entity.tag_names() {
145 if seen.insert(name) {
146 col_set.push(name);
147 }
148 }
149 }
150
151 col_set.sort_unstable();
152 let cols: Vec<HCol> = col_set.iter().map(|&n| HCol::new(n)).collect();
153 Ok(HGrid::from_parts_arc(HDict::new(), cols, results))
154}