Skip to main content

haystack_server/ops/
defs.rs

1//! The `defs` and `libs` ops — query the definition namespace.
2
3use 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
12/// POST /api/defs
13///
14/// Request may have a `filter` column with a filter string.
15/// Returns matching def records as a grid.
16pub async fn handle(
17    req: HttpRequest,
18    body: String,
19    state: web::Data<AppState>,
20) -> Result<HttpResponse, HaystackError> {
21    let content_type = req
22        .headers()
23        .get("Content-Type")
24        .and_then(|v| v.to_str().ok())
25        .unwrap_or("");
26    let accept = req
27        .headers()
28        .get("Accept")
29        .and_then(|v| v.to_str().ok())
30        .unwrap_or("");
31
32    let ns = state.namespace.read();
33
34    // Parse optional filter from request
35    let filter: Option<String> = if body.trim().is_empty() {
36        None
37    } else {
38        let request_grid = content::decode_request_grid(&body, content_type)
39            .map_err(|e| HaystackError::bad_request(format!("failed to decode request: {e}")))?;
40
41        request_grid.row(0).and_then(|row| match row.get("filter") {
42            Some(Kind::Str(s)) if !s.is_empty() => Some(s.clone()),
43            _ => None,
44        })
45    };
46
47    // Build def grid
48    let cols = vec![HCol::new("def"), HCol::new("lib"), HCol::new("doc")];
49
50    let defs = ns.defs();
51    let mut rows: Vec<HDict> = Vec::new();
52
53    for (symbol, def) in defs {
54        // If a filter is provided, only include defs whose symbol contains the filter
55        if let Some(ref f) = filter
56            && !symbol.contains(f.as_str())
57        {
58            continue;
59        }
60
61        let mut row = HDict::new();
62        row.set(
63            "def",
64            Kind::Symbol(haystack_core::kinds::Symbol::new(symbol)),
65        );
66        row.set(
67            "lib",
68            Kind::Symbol(haystack_core::kinds::Symbol::new(&def.lib)),
69        );
70        row.set("doc", Kind::Str(def.doc.clone()));
71        rows.push(row);
72    }
73
74    // Sort by symbol for deterministic output
75    rows.sort_by(|a, b| {
76        let a_name = match a.get("def") {
77            Some(Kind::Symbol(s)) => s.val(),
78            _ => "",
79        };
80        let b_name = match b.get("def") {
81            Some(Kind::Symbol(s)) => s.val(),
82            _ => "",
83        };
84        a_name.cmp(b_name)
85    });
86
87    let grid = HGrid::from_parts(HDict::new(), cols, rows);
88    let (encoded, ct) = content::encode_response_grid(&grid, accept)
89        .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
90
91    Ok(HttpResponse::Ok().content_type(ct).body(encoded))
92}
93
94/// POST /api/libs — returns a grid of library names.
95pub async fn handle_libs(
96    req: HttpRequest,
97    state: web::Data<AppState>,
98) -> Result<HttpResponse, HaystackError> {
99    let accept = req
100        .headers()
101        .get("Accept")
102        .and_then(|v| v.to_str().ok())
103        .unwrap_or("");
104
105    let ns = state.namespace.read();
106
107    let cols = vec![HCol::new("name"), HCol::new("version")];
108    let libs = ns.libs();
109    let mut rows: Vec<HDict> = libs
110        .values()
111        .map(|lib| {
112            let mut row = HDict::new();
113            row.set("name", Kind::Str(lib.name.clone()));
114            row.set("version", Kind::Str(lib.version.clone()));
115            row
116        })
117        .collect();
118
119    // Sort by name for deterministic output
120    rows.sort_by(|a, b| {
121        let a_name = match a.get("name") {
122            Some(Kind::Str(s)) => s.as_str(),
123            _ => "",
124        };
125        let b_name = match b.get("name") {
126            Some(Kind::Str(s)) => s.as_str(),
127            _ => "",
128        };
129        a_name.cmp(b_name)
130    });
131
132    let grid = HGrid::from_parts(HDict::new(), cols, rows);
133    let (encoded, ct) = content::encode_response_grid(&grid, accept)
134        .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
135
136    Ok(HttpResponse::Ok().content_type(ct).body(encoded))
137}