haystack_server/ops/
read.rs1use actix_web::{HttpRequest, HttpResponse, web};
38
39use haystack_core::data::{HCol, HDict, HGrid};
40use haystack_core::kinds::{HRef, Kind};
41
42use crate::content;
43use crate::error::HaystackError;
44use crate::state::AppState;
45
46pub async fn handle(
52 req: HttpRequest,
53 body: String,
54 state: web::Data<AppState>,
55) -> Result<HttpResponse, HaystackError> {
56 let content_type = req
57 .headers()
58 .get("Content-Type")
59 .and_then(|v| v.to_str().ok())
60 .unwrap_or("");
61 let accept = req
62 .headers()
63 .get("Accept")
64 .and_then(|v| v.to_str().ok())
65 .unwrap_or("");
66
67 let request_grid = content::decode_request_grid(&body, content_type)
68 .map_err(|e| HaystackError::bad_request(format!("failed to decode request: {e}")))?;
69
70 let result_grid = if request_grid.col("id").is_some() {
71 read_by_id(&request_grid, &state)
73 } else if request_grid.col("filter").is_some() {
74 read_by_filter(&request_grid, &state)
76 } else {
77 Err(HaystackError::bad_request(
78 "request must have 'id' or 'filter' column",
79 ))
80 }?;
81
82 let (encoded, ct) = content::encode_response_grid(&result_grid, accept)
83 .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
84
85 Ok(HttpResponse::Ok().content_type(ct).body(encoded))
86}
87
88fn read_by_filter(request_grid: &HGrid, state: &AppState) -> Result<HGrid, HaystackError> {
93 let row = request_grid
94 .row(0)
95 .ok_or_else(|| HaystackError::bad_request("request grid has no rows"))?;
96
97 let filter = match row.get("filter") {
98 Some(Kind::Str(s)) => s.as_str(),
99 _ => return Err(HaystackError::bad_request("filter must be a Str value")),
100 };
101
102 let limit = match row.get("limit") {
103 Some(Kind::Number(n)) => n.val as usize,
104 _ => 0, };
106
107 let effective_limit = if limit == 0 { usize::MAX } else { limit };
108 let is_wildcard = filter == "*";
109
110 let mut results: Vec<HDict> = if is_wildcard {
112 state.graph.all_entities()
114 } else {
115 let local_grid = state
116 .graph
117 .read_filter(filter, limit)
118 .map_err(|e| HaystackError::bad_request(format!("filter error: {e}")))?;
119 local_grid.rows
120 };
121
122 if results.len() > effective_limit {
124 results.truncate(effective_limit);
125 }
126
127 if results.len() < effective_limit {
129 let remaining = effective_limit - results.len();
130 if is_wildcard {
131 let federated = state.federation.all_cached_entities();
133 for entity in federated {
134 if results.len() >= effective_limit {
135 break;
136 }
137 results.push(entity);
138 }
139 } else {
140 match state.federation.filter_cached_entities(filter, remaining) {
142 Ok(federated) => results.extend(federated),
143 Err(e) => {
144 return Err(HaystackError::bad_request(format!(
145 "federation filter error: {e}"
146 )));
147 }
148 }
149 }
150 }
151
152 if results.is_empty() {
153 return Ok(HGrid::new());
154 }
155
156 let mut col_set: Vec<&str> = Vec::new();
158 let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
159 for entity in &results {
160 for name in entity.tag_names() {
161 if seen.insert(name) {
162 col_set.push(name);
163 }
164 }
165 }
166 col_set.sort_unstable();
167 let cols: Vec<HCol> = col_set.iter().map(|&n| HCol::new(n)).collect();
168
169 Ok(HGrid::from_parts(HDict::new(), cols, results))
170}
171
172fn read_by_id(request_grid: &HGrid, state: &AppState) -> Result<HGrid, HaystackError> {
178 let mut results: Vec<HDict> = Vec::new();
179 let mut unknown_ids: Vec<String> = Vec::new();
180
181 for row in request_grid.rows.iter() {
183 let ref_val = match row.get("id") {
184 Some(Kind::Ref(r)) => &r.val,
185 _ => continue,
186 };
187
188 if let Some(entity) = state.graph.get(ref_val) {
189 results.push(entity);
190 } else {
191 unknown_ids.push(ref_val.clone());
192 }
193 }
194
195 if !unknown_ids.is_empty() && !state.federation.connectors.is_empty() {
197 let id_refs: Vec<&str> = unknown_ids.iter().map(|s| s.as_str()).collect();
198 let (found, still_missing) = state.federation.batch_read_by_id(id_refs);
199 results.extend(found);
200
201 for id in still_missing {
203 let mut missing = HDict::new();
204 missing.set("id", Kind::Ref(HRef::from_val(id.as_str())));
205 results.push(missing);
206 }
207 } else {
208 for id in unknown_ids {
210 let mut missing = HDict::new();
211 missing.set("id", Kind::Ref(HRef::from_val(id.as_str())));
212 results.push(missing);
213 }
214 }
215
216 if results.is_empty() {
217 return Ok(HGrid::new());
218 }
219
220 let mut col_set: Vec<&str> = Vec::new();
222 let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
223 for entity in &results {
224 for name in entity.tag_names() {
225 if seen.insert(name) {
226 col_set.push(name);
227 }
228 }
229 }
230
231 col_set.sort_unstable();
232 let cols: Vec<HCol> = col_set.iter().map(|&n| HCol::new(n)).collect();
233 Ok(HGrid::from_parts(HDict::new(), cols, results))
234}