Skip to main content

sim_codec_json/
json_tree.rs

1//! Codec-owned JSON data model and bounded text boundary.
2
3use serde_json::{Map, Number, Value};
4use sim_codec::{DecodeBudget, DecodeLimits};
5use sim_kernel::{CodecId, Error, Result};
6
7/// A dependency-neutral JSON tree.
8///
9/// This is the public interchange model for guests that need JSON data without
10/// adopting the codec's parser implementation as part of their own API.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum JsonTree {
13    /// The JSON `null` value.
14    Null,
15    /// A JSON boolean.
16    Bool(bool),
17    /// A JSON number in its validated textual representation.
18    Number(String),
19    /// A JSON string.
20    String(String),
21    /// A JSON array.
22    Array(Vec<JsonTree>),
23    /// A JSON object.
24    Object(Vec<(String, JsonTree)>),
25}
26
27/// Parses JSON text into the codec-owned tree under default decode limits.
28pub fn parse_json(codec: CodecId, source: &str) -> Result<JsonTree> {
29    parse_json_with_limits(codec, source, DecodeLimits::default())
30}
31
32/// Parses JSON text into the codec-owned tree under explicit decode limits.
33pub fn parse_json_with_limits(
34    codec: CodecId,
35    source: &str,
36    limits: DecodeLimits,
37) -> Result<JsonTree> {
38    let mut budget = DecodeBudget::new(limits);
39    budget.check_input_bytes(codec, source.len())?;
40    let value = serde_json::from_str(source).map_err(|error| json_error(codec, error))?;
41    JsonTree::from_value(codec, value, &mut budget, 0)
42}
43
44/// Renders a codec-owned JSON tree as compact canonical text.
45pub fn render_json(codec: CodecId, tree: &JsonTree) -> Result<String> {
46    serde_json::to_string(&tree.to_value(codec)?).map_err(|error| json_error(codec, error))
47}
48
49impl JsonTree {
50    /// Constructs a JSON number from a finite binary floating-point value.
51    ///
52    /// Number validation and canonical text selection remain owned by the JSON
53    /// codec, so guest policies do not need their own number serializer.
54    pub fn number_from_f64(codec: CodecId, value: f64) -> Result<Self> {
55        Number::from_f64(value)
56            .map(|number| Self::Number(number.to_string()))
57            .ok_or_else(|| json_error(codec, "non-finite number"))
58    }
59
60    /// Reads a JSON number as a binary floating-point value.
61    pub fn number_as_f64(&self, codec: CodecId) -> Result<f64> {
62        let Self::Number(value) = self else {
63            return Err(json_error(codec, "JSON value is not a number"));
64        };
65        value
66            .parse::<f64>()
67            .map_err(|error| json_error(codec, error))
68    }
69
70    pub(crate) fn from_json_value(value: Value) -> Self {
71        match value {
72            Value::Null => Self::Null,
73            Value::Bool(value) => Self::Bool(value),
74            Value::Number(value) => Self::Number(value.to_string()),
75            Value::String(value) => Self::String(value),
76            Value::Array(values) => {
77                Self::Array(values.into_iter().map(Self::from_json_value).collect())
78            }
79            Value::Object(values) => Self::Object(
80                values
81                    .into_iter()
82                    .map(|(key, value)| (key, Self::from_json_value(value)))
83                    .collect(),
84            ),
85        }
86    }
87
88    pub(crate) fn to_json_value(&self, codec: CodecId) -> Result<Value> {
89        self.to_value(codec)
90    }
91
92    fn from_value(
93        codec: CodecId,
94        value: Value,
95        budget: &mut DecodeBudget,
96        depth: usize,
97    ) -> Result<Self> {
98        budget.enter_node(codec, depth)?;
99        match value {
100            Value::Null => Ok(Self::Null),
101            Value::Bool(value) => Ok(Self::Bool(value)),
102            Value::Number(value) => Ok(Self::Number(value.to_string())),
103            Value::String(value) => {
104                budget.check_string_bytes(codec, value.len())?;
105                Ok(Self::String(value))
106            }
107            Value::Array(values) => {
108                budget.check_collection_len(codec, values.len())?;
109                values
110                    .into_iter()
111                    .map(|value| Self::from_value(codec, value, budget, depth + 1))
112                    .collect::<Result<Vec<_>>>()
113                    .map(Self::Array)
114            }
115            Value::Object(values) => {
116                budget.check_collection_len(codec, values.len())?;
117                values
118                    .into_iter()
119                    .map(|(key, value)| {
120                        budget.check_string_bytes(codec, key.len())?;
121                        Ok((key, Self::from_value(codec, value, budget, depth + 1)?))
122                    })
123                    .collect::<Result<Vec<_>>>()
124                    .map(Self::Object)
125            }
126        }
127    }
128
129    fn to_value(&self, codec: CodecId) -> Result<Value> {
130        match self {
131            Self::Null => Ok(Value::Null),
132            Self::Bool(value) => Ok(Value::Bool(*value)),
133            Self::Number(value) => value
134                .parse::<Number>()
135                .map(Value::Number)
136                .map_err(|error| json_error(codec, error)),
137            Self::String(value) => Ok(Value::String(value.clone())),
138            Self::Array(values) => values
139                .iter()
140                .map(|value| value.to_value(codec))
141                .collect::<Result<Vec<_>>>()
142                .map(Value::Array),
143            Self::Object(values) => values
144                .iter()
145                .map(|(key, value)| Ok((key.clone(), value.to_value(codec)?)))
146                .collect::<Result<Map<_, _>>>()
147                .map(Value::Object),
148        }
149    }
150}
151
152fn json_error(codec: CodecId, error: impl std::fmt::Display) -> Error {
153    Error::CodecError {
154        codec,
155        message: error.to_string(),
156    }
157}