Skip to main content

polydat_core/library/
exactly_one.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! `exactly_one_value` — explicit unwrap of a unary structural body
5//! (SRD-66 §"Surface 4").
6//!
7//! The motivating use case has a CQL `describe keyspace` op whose
8//! body is a single row × single text column. To regex-match the
9//! schema text, the workload asserts unary shape and unwraps. No
10//! implicit modal projection — identical workload source against a
11//! non-unary body must surface a clear shape diagnostic, not silently
12//! diverge from intent.
13//!
14//! Push 1 implements the assertion against the existing `Value`
15//! variants (Str, Bool, U64, F64, VecF32, VecI32, None). Push 2 will
16//! settle the structural body type (`Json` or similar) and extend
17//! this node to walk row × column structure with the diagnostic
18//! format from SRD-66 §"Surface 4 §Semantics":
19//!
20//! ```text
21//! exactly_one_value: expected unary structure (1 row × 1 column),
22//!                    found <r> rows × <c> columns
23//! ```
24
25use crate::ast::Value;
26
27/// Assert that the input value is a unary structure and return its
28/// single cell as a String. See module docs.
29///
30/// **Output is always `Str`** (the function returns `String`, so the
31/// macro emits a fixed `PortType::Str` output port). The function
32/// accepts any `Value` variant the upstream wire produces and
33/// renders the unwrapped leaf to its display form. This matches the
34/// load-bearing workload pattern `schema_text := exactly_one_value(body)`
35/// where downstream nodes (regex_match, format, …) take `&str`.
36///
37/// A polymorphic-output variant that preserves leaf types
38/// (Bool/U64/F64/…) would require a port-type declaration that
39/// reflects the runtime unwrap result rather than the input port,
40/// which the macro can't express today without generic-over-Wire
41/// instantiation. When such a variant is needed it becomes its own
42/// node.
43#[crate::polydat_node(category = Diagnostic)]
44fn exactly_one_value(body: Value) -> String {
45    if crate::library::debug_nodes_enabled() {
46        // Per-cycle visibility into what the structural unwrap
47        // saw and produced. Body's display form is truncated for
48        // long Json arrays so the trace stays scannable.
49        let body_disp = body.to_display_string();
50        let snippet: String = body_disp.chars().take(400).collect();
51        let ellipsis = if body_disp.len() > snippet.len() {
52            "…"
53        } else {
54            ""
55        };
56        eprintln!(
57            "[DEBUG] exactly_one_value: body.variant={:?} body.len={} snippet={}{ellipsis}",
58            body.port_type(),
59            body_disp.len(),
60            snippet,
61        );
62    }
63    let leaf_value: Value = match &body {
64        // Already-scalar values pass through unchanged. They came
65        // from a body projection that already collapsed the row ×
66        // column structure.
67        Value::Str(_)
68        | Value::Bool(_)
69        | Value::U64(_)
70        | Value::I64(_)
71        | Value::U128(_)
72        | Value::I128(_)
73        | Value::F64(_) => body.clone(),
74
75        // Typed vector carriers: the structural shape is "1 row × 1
76        // column" iff the slice has exactly one element.
77        Value::VecF32(arc) => {
78            if arc.len() != 1 {
79                panic!(
80                    "exactly_one_value: expected unary structure \
81                     (1 row × 1 column), found vec_f32 of length {}",
82                    arc.len()
83                );
84            }
85            Value::F64(arc[0] as f64)
86        }
87        Value::VecI32(arc) => {
88            if arc.len() != 1 {
89                panic!(
90                    "exactly_one_value: expected unary structure \
91                     (1 row × 1 column), found vec_i32 of length {}",
92                    arc.len()
93                );
94            }
95            Value::I64(arc[0] as i64)
96        }
97        Value::VecF64(arc) => {
98            if arc.len() != 1 {
99                panic!(
100                    "exactly_one_value: expected unary structure \
101                     (1 row × 1 column), found vec_f64 of length {}",
102                    arc.len()
103                );
104            }
105            Value::F64(arc[0])
106        }
107        Value::VecI64(arc) => {
108            if arc.len() != 1 {
109                panic!(
110                    "exactly_one_value: expected unary structure \
111                     (1 row × 1 column), found vec_i64 of length {}",
112                    arc.len()
113                );
114            }
115            Value::I64(arc[0])
116        }
117        Value::VecF16(arc) => {
118            if arc.len() != 1 {
119                panic!(
120                    "exactly_one_value: expected unary structure \
121                     (1 row × 1 column), found vec_f16 of length {}",
122                    arc.len()
123                );
124            }
125            Value::F64(arc[0].to_f32() as f64)
126        }
127        Value::VecI16(arc) => {
128            if arc.len() != 1 {
129                panic!(
130                    "exactly_one_value: expected unary structure \
131                     (1 row × 1 column), found vec_i16 of length {}",
132                    arc.len()
133                );
134            }
135            Value::I64(arc[0] as i64)
136        }
137        Value::VecI8(arc) => {
138            if arc.len() != 1 {
139                panic!(
140                    "exactly_one_value: expected unary structure \
141                     (1 row × 1 column), found vec_i8 of length {}",
142                    arc.len()
143                );
144            }
145            Value::I64(arc[0] as i64)
146        }
147
148        Value::None => panic!(
149            "exactly_one_value: empty body (Value::None); the upstream \
150             op produced no result to unwrap"
151        ),
152
153        // Structural body walk: array = row dim, object = column
154        // dim, leaf = result. Unary shape = 1 × 1 × 1.
155        Value::Json(j) => unwrap_unary_json(j),
156
157        // Other carriers pass through (already collapsed).
158        // Register words render via their view's display form.
159        Value::Bytes(_) | Value::Ext(_) | Value::Handle(_) | Value::Reg128(_, _) => body.clone(),
160    };
161    // Render to String for the declared Str output port. Non-Str
162    // leaves render via the Value display form (Bool → "true"/"false",
163    // U64 → "42", F64 → "3.14", etc.).
164    match leaf_value {
165        Value::Str(s) => s.to_string(),
166        other => other.to_display_string(),
167    }
168}
169
170/// Walk a JSON value asserting unary row × column × leaf shape.
171/// Returns the matching `Value` variant for the leaf cell.
172///
173/// The shape diagnostic names actual dimensions when the input
174/// doesn't match the unary contract.
175fn unwrap_unary_json(j: &serde_json::Value) -> Value {
176    use serde_json::Value as J;
177    // Row dimension: an array. Length 0 / >1 → shape error.
178    let row = match j {
179        J::Array(arr) => match arr.len() {
180            0 => panic!(
181                "exactly_one_value: expected unary structure (1 row × 1 column), \
182                 found 0 rows"
183            ),
184            1 => &arr[0],
185            n => panic!(
186                "exactly_one_value: expected unary structure (1 row × 1 column), \
187                 found {n} rows"
188            ),
189        },
190        // No row wrapper — treat the whole value as the single
191        // row and continue to column inspection. Adapters that
192        // produce a single-row unwrapped projection (rare; CQL
193        // doesn't) take this path naturally.
194        other => other,
195    };
196    // Column dimension: an object. Length 0 / >1 → shape error.
197    let leaf = match row {
198        J::Object(obj) => match obj.len() {
199            0 => panic!(
200                "exactly_one_value: expected unary structure (1 row × 1 column), \
201                 found 1 row × 0 columns"
202            ),
203            1 => obj.values().next().expect("len==1"),
204            n => panic!(
205                "exactly_one_value: expected unary structure (1 row × 1 column), \
206                 found 1 row × {n} columns"
207            ),
208        },
209        // No column wrapper — the row IS the leaf. Common for
210        // non-tabular bodies (e.g. a HTTP body that's a bare
211        // string).
212        other => other,
213    };
214    match leaf {
215        J::String(s) => Value::Str(s.as_str().into()),
216        J::Bool(b) => Value::Bool(*b),
217        // Total, lossless number extraction mirroring
218        // serde_json::Number's three leaves (PosInt / NegInt /
219        // Float) — a negative integer lands in the honest signed
220        // carrier instead of degrading to F64.
221        J::Number(n) => {
222            if let Some(u) = n.as_u64() {
223                Value::U64(u)
224            } else if let Some(i) = n.as_i64() {
225                Value::I64(i)
226            } else if let Some(f) = n.as_f64() {
227                Value::F64(f)
228            } else {
229                panic!(
230                    "exactly_one_value: numeric leaf is not representable as u64, i64, or f64: {n}"
231                )
232            }
233        }
234        J::Null => panic!("exactly_one_value: leaf cell is null; expected a non-null value"),
235        // Nested structural leaf — the body has more than two
236        // levels of nesting. Not a unary shape per the SRD; the
237        // diagnostic names what was found.
238        J::Array(_) | J::Object(_) => panic!(
239            "exactly_one_value: leaf cell is itself structural ({}); \
240             expected a scalar (string, number, or boolean)",
241            describe_json_kind(leaf)
242        ),
243    }
244}
245
246fn describe_json_kind(j: &serde_json::Value) -> &'static str {
247    use serde_json::Value as J;
248    match j {
249        J::Null => "null",
250        J::Bool(_) => "bool",
251        J::Number(_) => "number",
252        J::String(_) => "string",
253        J::Array(_) => "array",
254        J::Object(_) => "object",
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use crate::ast::PolydatNode;
262
263    fn run(input: Value) -> Value {
264        // Macro emits `ExactlyOneValue::new(input_type)` for a
265        // PolyWire-in node; pass the input's port type so the
266        // input slot's port-type metadata matches the upstream
267        // wire. (Output port is fixed `Str` regardless of input.)
268        let node = ExactlyOneValue::new(input.port_type());
269        let mut out = [Value::None];
270        node.eval(&[input], &mut out);
271        out.into_iter().next().unwrap()
272    }
273
274    #[test]
275    fn passes_through_str() {
276        let v = run(Value::Str("hello".into()));
277        assert_eq!(v.as_str(), "hello");
278    }
279
280    #[test]
281    fn passes_through_bool() {
282        // Bool leaves render to "true"/"false" (Str output).
283        let v = run(Value::Bool(true));
284        assert_eq!(v.as_str(), "true");
285    }
286
287    #[test]
288    fn passes_through_u64() {
289        // Numeric leaves render to their display form.
290        let v = run(Value::U64(42));
291        assert_eq!(v.as_str(), "42");
292    }
293
294    #[test]
295    fn passes_through_f64() {
296        let v = run(Value::F64(2.5));
297        assert_eq!(v.as_str(), "2.5");
298    }
299
300    #[test]
301    fn unwraps_singleton_vec_f32() {
302        let v = Value::VecF32(crate::ast::SliceArc::from_vec(vec![1.5_f32]));
303        let out = run(v);
304        assert_eq!(out.as_str(), "1.5");
305    }
306
307    #[test]
308    #[should_panic(expected = "expected unary structure")]
309    fn rejects_multi_element_vec_f32() {
310        let v = Value::VecF32(crate::ast::SliceArc::from_vec(vec![1.0_f32, 2.0]));
311        run(v);
312    }
313
314    #[test]
315    #[should_panic(expected = "exactly_one_value: empty body")]
316    fn rejects_none() {
317        run(Value::None);
318    }
319
320    // ---------------------------------------------------------------
321    // SRD-66 §"Surface 4 §Semantics" — structural Json walk
322    // ---------------------------------------------------------------
323
324    #[test]
325    fn unwraps_unary_json_describe_keyspace_shape() {
326        let j = serde_json::json!([
327            {"create_statement": "VIRTUAL TABLE system_views.sai_column_indexes (\n  ...\n)"}
328        ]);
329        let out = run(Value::Json(std::sync::Arc::new(j)));
330        let s = out.as_str();
331        assert!(s.starts_with("VIRTUAL TABLE"), "got: {s:?}");
332    }
333
334    #[test]
335    fn unwraps_unary_json_string_leaf() {
336        let j = serde_json::json!([{"value": "hello"}]);
337        let out = run(Value::Json(std::sync::Arc::new(j)));
338        assert_eq!(out.as_str(), "hello");
339    }
340
341    #[test]
342    fn unwraps_unary_json_numeric_leaf() {
343        let j = serde_json::json!([{"n": 42}]);
344        let out = run(Value::Json(std::sync::Arc::new(j)));
345        assert_eq!(out.as_str(), "42");
346    }
347
348    #[test]
349    fn unwraps_unary_json_bool_leaf() {
350        let j = serde_json::json!([{"b": true}]);
351        let out = run(Value::Json(std::sync::Arc::new(j)));
352        assert_eq!(out.as_str(), "true");
353    }
354
355    #[test]
356    #[should_panic(expected = "found 0 rows")]
357    fn rejects_empty_json_array() {
358        run(Value::Json(std::sync::Arc::new(serde_json::json!([]))));
359    }
360
361    #[test]
362    #[should_panic(expected = "found 2 rows")]
363    fn rejects_multi_row_json() {
364        let j = serde_json::json!([{"a": 1}, {"a": 2}]);
365        run(Value::Json(std::sync::Arc::new(j)));
366    }
367
368    #[test]
369    #[should_panic(expected = "found 1 row × 2 columns")]
370    fn rejects_multi_column_json() {
371        let j = serde_json::json!([{"a": 1, "b": 2}]);
372        run(Value::Json(std::sync::Arc::new(j)));
373    }
374
375    #[test]
376    #[should_panic(expected = "leaf cell is null")]
377    fn rejects_json_null_leaf() {
378        let j = serde_json::json!([{"a": null}]);
379        run(Value::Json(std::sync::Arc::new(j)));
380    }
381
382    #[test]
383    #[should_panic(expected = "leaf cell is itself structural")]
384    fn rejects_json_nested_structural_leaf() {
385        let j = serde_json::json!([{"a": {"nested": 1}}]);
386        run(Value::Json(std::sync::Arc::new(j)));
387    }
388}