Skip to main content

whipplescript_core/
json.rs

1//! Generic JSON/string parsing utilities shared across the platform.
2//!
3//! These are provider- and package-agnostic helpers over `serde_json::Value`.
4//! They live in the leaf `whipplescript-core` crate so both the CLI surfaces and
5//! the wasm-kernel-hostable package registry validators (which cannot call back
6//! into the CLI binary) can reach them.
7
8use serde_json::Value;
9
10/// Read a required non-empty string field, or an actionable error naming
11/// `owner` and `field`.
12pub fn required_json_string(value: &Value, field: &str, owner: &str) -> Result<String, String> {
13    value
14        .get(field)
15        .and_then(Value::as_str)
16        .filter(|value| !value.trim().is_empty())
17        .map(str::to_owned)
18        .ok_or_else(|| format!("{owner} must have non-empty `{field}` string"))
19}
20
21/// Read an optional non-empty string field.
22pub fn optional_json_string(value: &Value, field: &str) -> Option<String> {
23    value
24        .get(field)
25        .and_then(Value::as_str)
26        .filter(|value| !value.trim().is_empty())
27        .map(str::to_owned)
28}
29
30/// Read the first present non-empty string among `fields`.
31pub fn optional_json_string_any(value: &Value, fields: &[&str]) -> Option<String> {
32    fields
33        .iter()
34        .find_map(|field| optional_json_string(value, field))
35}
36
37/// Read an optional string array, dropping empty entries.
38pub fn optional_json_string_array(value: &Value, field: &str) -> Option<Vec<String>> {
39    value.get(field).and_then(Value::as_array).map(|items| {
40        items
41            .iter()
42            .filter_map(Value::as_str)
43            .filter(|item| !item.trim().is_empty())
44            .map(str::to_owned)
45            .collect::<Vec<_>>()
46    })
47}
48
49/// Read a required array field by reference, or an actionable error.
50pub fn require_json_array_field<'a>(
51    value: &'a Value,
52    field: &str,
53    owner: &str,
54) -> Result<&'a Vec<Value>, String> {
55    value
56        .get(field)
57        .and_then(Value::as_array)
58        .ok_or_else(|| format!("{owner}.{field} must be an array"))
59}
60
61/// Join `values` as a comma-separated list of backtick-quoted tokens, for
62/// "expected one of ..." diagnostics.
63pub fn quoted_platform_values<'a>(values: impl IntoIterator<Item = &'a str>) -> String {
64    values
65        .into_iter()
66        .map(|value| format!("`{value}`"))
67        .collect::<Vec<_>>()
68        .join(", ")
69}