Skip to main content

copperlace/render/
value.rs

1use std::collections::BTreeMap;
2
3use serde::Serialize;
4
5use super::error::RenderError;
6use super::nodes::TextGeneratorNode;
7use super::state::LoopMetadata;
8use super::state::RenderState;
9
10/// Compiled structured document tree.
11///
12/// Text leaves reuse the same text-generation nodes as existing string render
13/// APIs. Arrays and objects remain structural here even when equivalent
14/// top-level entries are also indexed as text choice rules for compatibility.
15pub enum StructuredNode {
16    /// Object entries keyed by field name.
17    Object(BTreeMap<String, StructuredNode>),
18    /// Array entries in source order.
19    Array(Vec<StructuredArrayEntry>),
20    /// A text-generating template leaf.
21    Text(Box<dyn TextGeneratorNode>),
22    /// Numeric scalar.
23    Number(CopperlaceNumber),
24    /// Boolean scalar.
25    Boolean(bool),
26    /// Null scalar.
27    Null,
28}
29
30impl StructuredNode {
31    pub(crate) fn child(&self, path: &str) -> Option<&StructuredNode> {
32        if path.is_empty() {
33            return Some(self);
34        }
35
36        let mut node = self;
37        for segment in path.split('.') {
38            let StructuredNode::Object(values) = node else {
39                return None;
40            };
41            node = values.get(segment)?;
42        }
43        Some(node)
44    }
45
46    pub(crate) fn value_type(&self) -> &'static str {
47        match self {
48            StructuredNode::Object(_) => "object",
49            StructuredNode::Array(_) => "array",
50            StructuredNode::Text(_) => "string",
51            StructuredNode::Number(_) => "number",
52            StructuredNode::Boolean(_) => "boolean",
53            StructuredNode::Null => "null",
54        }
55    }
56
57    pub(crate) fn generate_text(&self, state: &mut RenderState) -> Result<String, RenderError> {
58        match self {
59            StructuredNode::Text(node) => node.generate_text(state),
60            StructuredNode::Number(value) => Ok(value.to_string()),
61            StructuredNode::Boolean(value) => Ok(value.to_string()),
62            StructuredNode::Null => Ok("null".to_string()),
63            StructuredNode::Object(_) => Err(RenderError::UnsupportedValue("object".to_string())),
64            StructuredNode::Array(_) => Err(RenderError::UnsupportedValue("array".to_string())),
65        }
66    }
67
68    pub(crate) fn generate_value(
69        &self,
70        state: &mut RenderState,
71    ) -> Result<CopperlaceValue, RenderError> {
72        match self {
73            StructuredNode::Object(values) => values
74                .iter()
75                .map(|(key, value)| Ok((key.clone(), value.generate_value(state)?)))
76                .collect::<Result<BTreeMap<_, _>, _>>()
77                .map(CopperlaceValue::Object),
78            StructuredNode::Array(values) => {
79                let mut rendered = Vec::new();
80                for value in values {
81                    value.append_values(state, &mut rendered)?;
82                }
83                Ok(CopperlaceValue::Array(rendered))
84            }
85            StructuredNode::Text(node) => node.generate_text(state).map(CopperlaceValue::String),
86            StructuredNode::Number(value) => Ok(CopperlaceValue::Number(*value)),
87            StructuredNode::Boolean(value) => Ok(CopperlaceValue::Boolean(*value)),
88            StructuredNode::Null => Ok(CopperlaceValue::Null),
89        }
90    }
91}
92
93/// One compiled entry in a structured array.
94///
95/// Fixed entries emit one structured value. Iteration entries emit one value
96/// for each source element. The internal representation is kept private so
97/// array rendering and template compilation remain separate concerns.
98pub struct StructuredArrayEntry {
99    renderer: Box<dyn StructuredArrayEntryRenderer>,
100}
101
102trait StructuredArrayEntryRenderer {
103    fn append_values(
104        &self,
105        state: &mut RenderState,
106        output: &mut Vec<CopperlaceValue>,
107    ) -> Result<(), RenderError>;
108
109    fn value_node(&self) -> Option<&StructuredNode>;
110
111    fn iteration(&self) -> Option<(&str, &str, &StructuredNode)>;
112}
113
114struct FixedStructuredArrayEntry {
115    value: StructuredNode,
116}
117
118struct ForEachStructuredArrayEntry {
119    variable_name: String,
120    source_name: String,
121    template: StructuredNode,
122}
123
124impl StructuredArrayEntry {
125    /// Creates a fixed structured array entry.
126    pub fn from_value(value: StructuredNode) -> Self {
127        StructuredArrayEntry {
128            renderer: Box::new(FixedStructuredArrayEntry { value }),
129        }
130    }
131
132    pub(crate) fn for_each(
133        variable_name: String,
134        source_name: String,
135        template: StructuredNode,
136    ) -> Self {
137        StructuredArrayEntry {
138            renderer: Box::new(ForEachStructuredArrayEntry {
139                variable_name,
140                source_name,
141                template,
142            }),
143        }
144    }
145
146    /// Returns the fixed value for a non-iterating entry.
147    pub fn value_node(&self) -> Option<&StructuredNode> {
148        self.renderer.value_node()
149    }
150
151    /// Returns the loop variable, source, and template for an iteration entry.
152    pub fn iteration(&self) -> Option<(&str, &str, &StructuredNode)> {
153        self.renderer.iteration()
154    }
155
156    pub(crate) fn append_values(
157        &self,
158        state: &mut RenderState,
159        output: &mut Vec<CopperlaceValue>,
160    ) -> Result<(), RenderError> {
161        self.renderer.append_values(state, output)
162    }
163}
164
165impl StructuredArrayEntryRenderer for FixedStructuredArrayEntry {
166    fn append_values(
167        &self,
168        state: &mut RenderState,
169        output: &mut Vec<CopperlaceValue>,
170    ) -> Result<(), RenderError> {
171        output.push(self.value.generate_value(state)?);
172        Ok(())
173    }
174
175    fn value_node(&self) -> Option<&StructuredNode> {
176        Some(&self.value)
177    }
178
179    fn iteration(&self) -> Option<(&str, &str, &StructuredNode)> {
180        None
181    }
182}
183
184impl StructuredArrayEntryRenderer for ForEachStructuredArrayEntry {
185    fn append_values(
186        &self,
187        state: &mut RenderState,
188        output: &mut Vec<CopperlaceValue>,
189    ) -> Result<(), RenderError> {
190        let elements = state.iterable_elements(&self.source_name)?;
191        let length = elements.len();
192        for (index, element) in elements.into_iter().enumerate() {
193            state.push_iteration_scope(
194                &self.variable_name,
195                element,
196                LoopMetadata { index, length },
197            );
198            let rendered = self.template.generate_value(state);
199            state.pop_iteration_scope();
200            output.push(rendered?);
201        }
202        Ok(())
203    }
204
205    fn value_node(&self) -> Option<&StructuredNode> {
206        None
207    }
208
209    fn iteration(&self) -> Option<(&str, &str, &StructuredNode)> {
210        Some((&self.variable_name, &self.source_name, &self.template))
211    }
212}
213
214impl From<StructuredNode> for StructuredArrayEntry {
215    fn from(value: StructuredNode) -> Self {
216        StructuredArrayEntry::from_value(value)
217    }
218}
219
220/// Native Copperlace structured render result.
221#[derive(Debug, Clone, PartialEq)]
222pub enum CopperlaceValue {
223    /// Object entries keyed by field name.
224    Object(BTreeMap<String, CopperlaceValue>),
225    /// Array entries in render order.
226    Array(Vec<CopperlaceValue>),
227    /// String scalar.
228    String(String),
229    /// Numeric scalar.
230    Number(CopperlaceNumber),
231    /// Boolean scalar.
232    Boolean(bool),
233    /// Null scalar.
234    Null,
235}
236
237impl CopperlaceValue {
238    pub(crate) fn child(&self, path: &str) -> Option<&CopperlaceValue> {
239        if path.is_empty() {
240            return Some(self);
241        }
242
243        let mut value = self;
244        for segment in path.split('.') {
245            let CopperlaceValue::Object(values) = value else {
246                return None;
247            };
248            value = values.get(segment)?;
249        }
250        Some(value)
251    }
252
253    pub(crate) fn value_type(&self) -> &'static str {
254        match self {
255            CopperlaceValue::Object(_) => "object",
256            CopperlaceValue::Array(_) => "array",
257            CopperlaceValue::String(_) => "string",
258            CopperlaceValue::Number(_) => "number",
259            CopperlaceValue::Boolean(_) => "boolean",
260            CopperlaceValue::Null => "null",
261        }
262    }
263
264    pub(crate) fn to_rendered_text(&self) -> Result<String, RenderError> {
265        match self {
266            CopperlaceValue::String(value) => Ok(value.clone()),
267            CopperlaceValue::Number(value) => Ok(value.to_string()),
268            CopperlaceValue::Boolean(value) => Ok(value.to_string()),
269            CopperlaceValue::Null => Ok("null".to_string()),
270            CopperlaceValue::Object(_) => Err(RenderError::UnsupportedValue("object".to_string())),
271            CopperlaceValue::Array(_) => Err(RenderError::UnsupportedValue("array".to_string())),
272        }
273    }
274
275    /// Converts this value into a JSON value.
276    pub fn into_json_value(self) -> serde_json::Value {
277        match self {
278            CopperlaceValue::Object(values) => serde_json::Value::Object(
279                values
280                    .into_iter()
281                    .map(|(key, value)| (key, value.into_json_value()))
282                    .collect(),
283            ),
284            CopperlaceValue::Array(values) => serde_json::Value::Array(
285                values
286                    .into_iter()
287                    .map(CopperlaceValue::into_json_value)
288                    .collect(),
289            ),
290            CopperlaceValue::String(value) => serde_json::Value::String(value),
291            CopperlaceValue::Number(value) => value.into_json_number(),
292            CopperlaceValue::Boolean(value) => serde_json::Value::Bool(value),
293            CopperlaceValue::Null => serde_json::Value::Null,
294        }
295    }
296
297    /// Converts this value into a JSON value without consuming it.
298    pub fn to_json_value(&self) -> serde_json::Value {
299        self.clone().into_json_value()
300    }
301
302    /// Serializes this value as compact JSON.
303    pub fn to_compact_json(&self) -> Result<String, RenderError> {
304        serde_json::to_string(&self.to_json_value())
305            .map_err(|error| RenderError::JsonSerialization(error.to_string()))
306    }
307
308    /// Serializes this value as formatted JSON using tabs for indentation.
309    pub fn to_formatted_json(&self) -> Result<String, RenderError> {
310        let mut output = Vec::new();
311        let formatter = serde_json::ser::PrettyFormatter::with_indent(b"\t");
312        let mut serializer = serde_json::Serializer::with_formatter(&mut output, formatter);
313        self.to_json_value()
314            .serialize(&mut serializer)
315            .map_err(|error| RenderError::JsonSerialization(error.to_string()))?;
316        String::from_utf8(output).map_err(|error| RenderError::JsonSerialization(error.to_string()))
317    }
318}
319
320/// Numeric scalar used by structured Copperlace values.
321#[derive(Debug, Clone, Copy, PartialEq)]
322pub enum CopperlaceNumber {
323    /// Integer value representable as `i64`.
324    Integer(i64),
325    /// Unsigned integer value larger than `i64::MAX`.
326    Unsigned(u64),
327    /// Floating-point value representable as finite `f64`.
328    Float(f64),
329}
330
331impl std::fmt::Display for CopperlaceNumber {
332    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333        match self {
334            CopperlaceNumber::Integer(value) => value.fmt(formatter),
335            CopperlaceNumber::Unsigned(value) => value.fmt(formatter),
336            CopperlaceNumber::Float(value) => {
337                let Some(number) = serde_json::Number::from_f64(*value) else {
338                    return Err(std::fmt::Error);
339                };
340                number.fmt(formatter)
341            }
342        }
343    }
344}
345
346impl CopperlaceNumber {
347    pub(crate) fn from_json_number(number: serde_json::Number) -> Result<Self, RenderError> {
348        if let Some(value) = number.as_i64() {
349            return Ok(CopperlaceNumber::Integer(value));
350        }
351        if let Some(value) = number.as_u64() {
352            return Ok(CopperlaceNumber::Unsigned(value));
353        }
354        let Some(value) = number.as_f64() else {
355            return Err(RenderError::UnsupportedValue(
356                "number must be representable as i64, u64, or f64".to_string(),
357            ));
358        };
359        if !value.is_finite() {
360            return Err(RenderError::UnsupportedValue(
361                "number must be finite".to_string(),
362            ));
363        }
364        Ok(CopperlaceNumber::Float(value))
365    }
366
367    fn into_json_number(self) -> serde_json::Value {
368        match self {
369            CopperlaceNumber::Integer(value) => serde_json::Value::Number(value.into()),
370            CopperlaceNumber::Unsigned(value) => serde_json::Value::Number(value.into()),
371            CopperlaceNumber::Float(value) => serde_json::Number::from_f64(value)
372                .map(serde_json::Value::Number)
373                .unwrap_or(serde_json::Value::Null),
374        }
375    }
376}