Skip to main content

liminal_sdk/
prev.rs

1//! The one substitution the seat's `flow` and a line's stages share.
2//!
3//! `{"$prev": "<JSON pointer>"}` and `{"$item": "<JSON pointer>"}` objects are filled from
4//! the previous reply and a fan-out's element (SDK-017 R4; NET-031 R1, R5).
5
6use alloc::format;
7use alloc::string::String;
8
9use serde_json::{Map, Value};
10
11/// The key naming a pointer into the previous reply.
12pub const PREV: &str = "$prev";
13
14/// The key naming a pointer into a fan-out's element.
15pub const ITEM: &str = "$item";
16
17/// `input` with every `{"$prev": pointer}` object replaced from `prev`.
18///
19/// # Errors
20/// The pointer that names nothing in `prev`, or a `$prev` with no previous reply.
21pub fn fill(input: &Value, prev: Option<&Value>) -> Result<Value, String> {
22    fill_with(input, prev, None)
23}
24
25/// `input` with every `{"$prev": pointer}` object replaced from `prev` and every
26/// `{"$item": pointer}` object from `item`.
27///
28/// # Errors
29/// The pointer that names nothing, a `$prev` with no previous reply, or an `$item` outside
30/// a fan-out.
31pub fn fill_with(
32    input: &Value,
33    prev: Option<&Value>,
34    item: Option<&Value>,
35) -> Result<Value, String> {
36    match input {
37        Value::Object(object) => {
38            if let Some(pointer) = sole(object, PREV) {
39                let prev = prev.ok_or_else(|| format!("{PREV} {pointer:?} in the first step"))?;
40                return prev.pointer(pointer).cloned().ok_or_else(|| {
41                    format!("{PREV} {pointer:?} names nothing in the previous reply")
42                });
43            }
44            if let Some(pointer) = sole(object, ITEM) {
45                let item = item.ok_or_else(|| format!("{ITEM} {pointer:?} outside a fan-out"))?;
46                return item
47                    .pointer(pointer)
48                    .cloned()
49                    .ok_or_else(|| format!("{ITEM} {pointer:?} names nothing in the element"));
50            }
51            let mut filled = Map::with_capacity(object.len());
52            for (key, value) in object {
53                filled.insert(key.clone(), fill_with(value, prev, item)?);
54            }
55            Ok(Value::Object(filled))
56        }
57        Value::Array(items) => items
58            .iter()
59            .map(|value| fill_with(value, prev, item))
60            .collect::<Result<_, _>>()
61            .map(Value::Array),
62        other => Ok(other.clone()),
63    }
64}
65
66/// Whether `input` names `$prev` anywhere, so a first stage that does is refused before it
67/// is kept (NET-031 R1).
68#[must_use]
69pub fn names_prev(input: &Value) -> bool {
70    match input {
71        Value::Object(object) => sole(object, PREV).is_some() || object.values().any(names_prev),
72        Value::Array(items) => items.iter().any(names_prev),
73        _ => false,
74    }
75}
76
77/// The pointer of an object whose one member is `key` with a string value.
78fn sole<'a>(object: &'a Map<String, Value>, key: &str) -> Option<&'a str> {
79    match (object.len(), object.get(key)) {
80        (1, Some(Value::String(pointer))) => Some(pointer),
81        _ => None,
82    }
83}
84
85#[cfg(test)]
86#[path = "prev_tests.rs"]
87mod tests;