mint_cli/layout/
used_values.rs1use serde_json::{Map, Number, Value};
2
3use crate::layout::error::LayoutError;
4use crate::layout::value::DataValue;
5
6pub trait ValueSink {
8 fn record_value(&mut self, path: &[String], value: Value) -> Result<(), LayoutError>;
10}
11
12#[derive(Debug, Default)]
14pub struct ValueCollector {
15 root: Map<String, Value>,
16}
17
18impl ValueCollector {
19 pub fn new() -> Self {
21 Self { root: Map::new() }
22 }
23
24 pub fn into_value(self) -> Value {
26 Value::Object(self.root)
27 }
28}
29
30impl ValueSink for ValueCollector {
31 fn record_value(&mut self, path: &[String], value: Value) -> Result<(), LayoutError> {
32 insert_value(&mut self.root, path, value)
33 }
34}
35
36pub struct NoopValueSink;
38
39impl ValueSink for NoopValueSink {
40 fn record_value(&mut self, _path: &[String], _value: Value) -> Result<(), LayoutError> {
41 Ok(())
42 }
43}
44
45pub fn data_value_to_json(value: &DataValue) -> Result<Value, LayoutError> {
46 match value {
47 DataValue::Bool(v) => Ok(Value::Number(Number::from(if *v { 1 } else { 0 }))),
48 DataValue::U64(v) => Ok(Value::Number(Number::from(*v))),
49 DataValue::I64(v) => Ok(Value::Number(Number::from(*v))),
50 DataValue::F64(v) => Number::from_f64(*v).map(Value::Number).ok_or_else(|| {
51 LayoutError::DataValueExportFailed(
52 "Non-finite float cannot be serialized to JSON.".to_string(),
53 )
54 }),
55 DataValue::Str(v) => Ok(Value::String(v.clone())),
56 }
57}
58
59pub fn i128_to_json(value: i128) -> Result<Value, LayoutError> {
60 if value >= 0 {
61 let unsigned = u64::try_from(value).map_err(|_| {
62 LayoutError::DataValueExportFailed("Value out of range for JSON number.".to_string())
63 })?;
64 Ok(Value::Number(Number::from(unsigned)))
65 } else {
66 let signed = i64::try_from(value).map_err(|_| {
67 LayoutError::DataValueExportFailed("Value out of range for JSON number.".to_string())
68 })?;
69 Ok(Value::Number(Number::from(signed)))
70 }
71}
72
73pub fn array_to_json(values: &[DataValue]) -> Result<Value, LayoutError> {
74 let mut out = Vec::with_capacity(values.len());
75 for value in values {
76 out.push(data_value_to_json(value)?);
77 }
78 Ok(Value::Array(out))
79}
80
81pub fn array_2d_to_json(values: &[Vec<DataValue>]) -> Result<Value, LayoutError> {
82 let mut out = Vec::with_capacity(values.len());
83 for row in values {
84 out.push(array_to_json(row)?);
85 }
86 Ok(Value::Array(out))
87}
88
89fn insert_value(
90 root: &mut Map<String, Value>,
91 path: &[String],
92 value: Value,
93) -> Result<(), LayoutError> {
94 if path.is_empty() {
95 return Err(LayoutError::DataValueExportFailed(
96 "Cannot record value with empty path.".to_string(),
97 ));
98 }
99
100 let key = &path[0];
101 if path.len() == 1 {
102 if root.contains_key(key) {
103 return Err(LayoutError::DataValueExportFailed(format!(
104 "Duplicate value path '{}'.",
105 path.join(".")
106 )));
107 }
108 root.insert(key.clone(), value);
109 return Ok(());
110 }
111
112 let entry = root
113 .entry(key.clone())
114 .or_insert_with(|| Value::Object(Map::new()));
115
116 match entry {
117 Value::Object(child) => insert_value(child, &path[1..], value),
118 _ => Err(LayoutError::DataValueExportFailed(format!(
119 "Path '{}' collides with existing value.",
120 path.join(".")
121 ))),
122 }
123}