Skip to main content

haystack_server/ops/
point_write.rs

1//! The `pointWrite` op — write a value to a writable point.
2
3use axum::extract::State;
4use axum::http::HeaderMap;
5use axum::response::{IntoResponse, Response};
6
7use haystack_core::data::{HDict, HGrid};
8use haystack_core::kinds::Kind;
9
10use crate::content;
11use crate::error::HaystackError;
12use crate::state::SharedState;
13
14/// POST /api/pointWrite
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 request_grid = content::decode_request_grid(&body, content_type)
30        .map_err(|e| HaystackError::bad_request(format!("failed to decode request: {e}")))?;
31
32    for row in request_grid.rows.iter() {
33        let ref_val = match row.get("id") {
34            Some(Kind::Ref(r)) => r.val.clone(),
35            _ => continue,
36        };
37
38        let level = match row.get("level") {
39            Some(Kind::Number(n)) => n.val as u32,
40            _ => 17, // Default level
41        };
42
43        if !(1..=17).contains(&level) {
44            return Err(HaystackError::bad_request(format!(
45                "level must be between 1 and 17, got {level}"
46            )));
47        }
48
49        if !state.graph.contains(&ref_val) {
50            return Err(HaystackError::not_found(format!(
51                "entity not found: {ref_val}"
52            )));
53        }
54
55        // Check that the target entity has the `writable` marker tag
56        let entity = state
57            .graph
58            .get(&ref_val)
59            .ok_or_else(|| HaystackError::not_found(format!("entity not found: {ref_val}")))?;
60        if !entity.has("writable") {
61            return Err(HaystackError::bad_request(format!(
62                "entity '{ref_val}' is not writable"
63            )));
64        }
65
66        // Get the value to write
67        if let Some(val) = row.get("val") {
68            let mut changes = HDict::new();
69            changes.set("curVal", val.clone());
70            changes.set(
71                "writeLevel",
72                Kind::Number(haystack_core::kinds::Number::unitless(level as f64)),
73            );
74            state
75                .graph
76                .update(&ref_val, changes)
77                .map_err(|e| HaystackError::bad_request(format!("write failed: {e}")))?;
78        }
79    }
80
81    // Return empty grid on success
82    let grid = HGrid::new();
83    let (encoded, ct) = content::encode_response_grid(&grid, accept)
84        .map_err(|e| HaystackError::internal(format!("encoding error: {e}")))?;
85
86    Ok(([(axum::http::header::CONTENT_TYPE, ct)], encoded).into_response())
87}