Skip to main content

haystack_server/ops/
defs.rs

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