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::RenderState;
8
9/// Compiled structured document tree.
10///
11/// Text leaves reuse the same text-generation nodes as existing string render
12/// APIs. Arrays and objects remain structural here even when equivalent
13/// top-level entries are also indexed as text choice rules for compatibility.
14pub enum StructuredNode {
15    /// Object entries keyed by field name.
16    Object(BTreeMap<String, StructuredNode>),
17    /// Array entries in source order.
18    Array(Vec<StructuredNode>),
19    /// A text-generating template leaf.
20    Text(Box<dyn TextGeneratorNode>),
21    /// Numeric scalar.
22    Number(CopperlaceNumber),
23    /// Boolean scalar.
24    Boolean(bool),
25    /// Null scalar.
26    Null,
27}
28
29impl StructuredNode {
30    pub(crate) fn generate_value(
31        &self,
32        state: &mut RenderState,
33    ) -> Result<CopperlaceValue, RenderError> {
34        match self {
35            StructuredNode::Object(values) => values
36                .iter()
37                .map(|(key, value)| Ok((key.clone(), value.generate_value(state)?)))
38                .collect::<Result<BTreeMap<_, _>, _>>()
39                .map(CopperlaceValue::Object),
40            StructuredNode::Array(values) => values
41                .iter()
42                .map(|value| value.generate_value(state))
43                .collect::<Result<Vec<_>, _>>()
44                .map(CopperlaceValue::Array),
45            StructuredNode::Text(node) => node.generate_text(state).map(CopperlaceValue::String),
46            StructuredNode::Number(value) => Ok(CopperlaceValue::Number(*value)),
47            StructuredNode::Boolean(value) => Ok(CopperlaceValue::Boolean(*value)),
48            StructuredNode::Null => Ok(CopperlaceValue::Null),
49        }
50    }
51}
52
53/// Native Copperlace structured render result.
54#[derive(Debug, Clone, PartialEq)]
55pub enum CopperlaceValue {
56    /// Object entries keyed by field name.
57    Object(BTreeMap<String, CopperlaceValue>),
58    /// Array entries in render order.
59    Array(Vec<CopperlaceValue>),
60    /// String scalar.
61    String(String),
62    /// Numeric scalar.
63    Number(CopperlaceNumber),
64    /// Boolean scalar.
65    Boolean(bool),
66    /// Null scalar.
67    Null,
68}
69
70impl CopperlaceValue {
71    /// Converts this value into a JSON value.
72    pub fn into_json_value(self) -> serde_json::Value {
73        match self {
74            CopperlaceValue::Object(values) => serde_json::Value::Object(
75                values
76                    .into_iter()
77                    .map(|(key, value)| (key, value.into_json_value()))
78                    .collect(),
79            ),
80            CopperlaceValue::Array(values) => serde_json::Value::Array(
81                values
82                    .into_iter()
83                    .map(CopperlaceValue::into_json_value)
84                    .collect(),
85            ),
86            CopperlaceValue::String(value) => serde_json::Value::String(value),
87            CopperlaceValue::Number(value) => value.into_json_number(),
88            CopperlaceValue::Boolean(value) => serde_json::Value::Bool(value),
89            CopperlaceValue::Null => serde_json::Value::Null,
90        }
91    }
92
93    /// Converts this value into a JSON value without consuming it.
94    pub fn to_json_value(&self) -> serde_json::Value {
95        self.clone().into_json_value()
96    }
97
98    /// Serializes this value as compact JSON.
99    pub fn to_compact_json(&self) -> Result<String, RenderError> {
100        serde_json::to_string(&self.to_json_value())
101            .map_err(|error| RenderError::JsonSerialization(error.to_string()))
102    }
103
104    /// Serializes this value as formatted JSON using tabs for indentation.
105    pub fn to_formatted_json(&self) -> Result<String, RenderError> {
106        let mut output = Vec::new();
107        let formatter = serde_json::ser::PrettyFormatter::with_indent(b"\t");
108        let mut serializer = serde_json::Serializer::with_formatter(&mut output, formatter);
109        self.to_json_value()
110            .serialize(&mut serializer)
111            .map_err(|error| RenderError::JsonSerialization(error.to_string()))?;
112        String::from_utf8(output).map_err(|error| RenderError::JsonSerialization(error.to_string()))
113    }
114}
115
116/// Numeric scalar used by structured Copperlace values.
117#[derive(Debug, Clone, Copy, PartialEq)]
118pub enum CopperlaceNumber {
119    /// Integer value representable as `i64`.
120    Integer(i64),
121    /// Unsigned integer value larger than `i64::MAX`.
122    Unsigned(u64),
123    /// Floating-point value representable as finite `f64`.
124    Float(f64),
125}
126
127impl CopperlaceNumber {
128    pub(crate) fn from_json_number(number: serde_json::Number) -> Result<Self, RenderError> {
129        if let Some(value) = number.as_i64() {
130            return Ok(CopperlaceNumber::Integer(value));
131        }
132        if let Some(value) = number.as_u64() {
133            return Ok(CopperlaceNumber::Unsigned(value));
134        }
135        let Some(value) = number.as_f64() else {
136            return Err(RenderError::UnsupportedValue(
137                "number must be representable as i64, u64, or f64".to_string(),
138            ));
139        };
140        if !value.is_finite() {
141            return Err(RenderError::UnsupportedValue(
142                "number must be finite".to_string(),
143            ));
144        }
145        Ok(CopperlaceNumber::Float(value))
146    }
147
148    fn into_json_number(self) -> serde_json::Value {
149        match self {
150            CopperlaceNumber::Integer(value) => serde_json::Value::Number(value.into()),
151            CopperlaceNumber::Unsigned(value) => serde_json::Value::Number(value.into()),
152            CopperlaceNumber::Float(value) => serde_json::Number::from_f64(value)
153                .map(serde_json::Value::Number)
154                .unwrap_or(serde_json::Value::Null),
155        }
156    }
157}