1use std::collections::BTreeMap;
4use std::fmt;
5
6use crate::color::Color;
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum Value {
11 Null,
12 Bool(bool),
13 Number(f64),
14 String(String),
15 Color(Color),
16 Array(Vec<Value>),
17 Object(BTreeMap<String, Value>),
18 Image {
20 name: String,
21 available: bool,
22 },
23 Formatted(Vec<FormatSection>),
25 NumberArray(Vec<f64>),
27 ColorArray(Vec<Color>),
29 Padding([f64; 4]),
31 Projection(Projection),
33 Collator {
35 case_sensitive: bool,
36 diacritic_sensitive: bool,
37 locale: Option<String>,
38 },
39}
40
41#[derive(Debug, Clone, PartialEq)]
43pub enum Projection {
44 Named(String),
45 Transition {
46 from: String,
47 to: String,
48 transition: f64,
49 },
50}
51
52#[derive(Debug, Clone, PartialEq)]
54pub struct FormatSection {
55 pub text: String,
56 pub image: Option<(String, bool)>,
58 pub scale: Option<f64>,
59 pub font_stack: Option<String>,
60 pub text_color: Option<Color>,
61 pub vertical_align: Option<String>,
62}
63
64impl Value {
65 pub fn type_name(&self) -> &'static str {
67 match self {
68 Value::Null => "null",
69 Value::Bool(_) => "boolean",
70 Value::Number(_) => "number",
71 Value::String(_) => "string",
72 Value::Color(_) => "color",
73 Value::Array(_) => "array",
74 Value::Object(_) => "object",
75 Value::Image { .. } => "resolvedImage",
76 Value::Formatted(_) => "formatted",
77 Value::NumberArray(_) => "numberArray",
78 Value::ColorArray(_) => "colorArray",
79 Value::Padding(_) => "padding",
80 Value::Projection(_) => "projectionDefinition",
81 Value::Collator { .. } => "collator",
82 }
83 }
84
85 pub fn as_number(&self) -> Option<f64> {
86 match self {
87 Value::Number(n) => Some(*n),
88 _ => None,
89 }
90 }
91
92 pub fn as_bool(&self) -> Option<bool> {
93 match self {
94 Value::Bool(b) => Some(*b),
95 _ => None,
96 }
97 }
98
99 pub fn as_str(&self) -> Option<&str> {
100 match self {
101 Value::String(s) => Some(s),
102 _ => None,
103 }
104 }
105
106 pub fn is_truthy(&self) -> bool {
108 match self {
109 Value::Null => false,
110 Value::Bool(b) => *b,
111 Value::Number(n) => *n != 0.0 && !n.is_nan(),
112 Value::String(s) => !s.is_empty(),
113 _ => true,
114 }
115 }
116
117 pub fn from_json(json: &serde_json::Value) -> Value {
120 match json {
121 serde_json::Value::Null => Value::Null,
122 serde_json::Value::Bool(b) => Value::Bool(*b),
123 serde_json::Value::Number(n) => Value::Number(n.as_f64().unwrap_or(f64::NAN)),
124 serde_json::Value::String(s) => Value::String(s.clone()),
125 serde_json::Value::Array(a) => Value::Array(a.iter().map(Value::from_json).collect()),
126 serde_json::Value::Object(o) => Value::Object(
127 o.iter()
128 .map(|(k, v)| (k.clone(), Value::from_json(v)))
129 .collect(),
130 ),
131 }
132 }
133}
134
135impl fmt::Display for Value {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 match self {
138 Value::Null => write!(f, ""),
139 Value::Bool(b) => write!(f, "{b}"),
140 Value::Number(n) => write!(f, "{}", format_number(*n)),
141 Value::String(s) => write!(f, "{s}"),
142 Value::Color(c) => write!(f, "{c}"),
143 Value::Array(a) => {
144 let parts: Vec<String> = a.iter().map(|v| v.to_string()).collect();
145 write!(f, "{}", parts.join(","))
146 }
147 Value::Object(_) => write!(f, "{self:?}"),
148 Value::Image { name, .. } => write!(f, "{name}"),
149 Value::Formatted(sections) => {
150 for s in sections {
151 write!(f, "{}", s.text)?;
152 }
153 Ok(())
154 }
155 Value::NumberArray(v) => {
156 let parts: Vec<String> = v.iter().map(|n| format_number(*n)).collect();
157 write!(f, "{}", parts.join(","))
158 }
159 Value::ColorArray(v) => {
160 let parts: Vec<String> = v.iter().map(|c| c.to_string()).collect();
161 write!(f, "{}", parts.join(","))
162 }
163 Value::Padding(v) => {
164 let parts: Vec<String> = v.iter().map(|n| format_number(*n)).collect();
165 write!(f, "{}", parts.join(","))
166 }
167 Value::Projection(Projection::Named(s)) => write!(f, "{s}"),
168 Value::Projection(_) => write!(f, "{self:?}"),
169 Value::Collator { .. } => write!(f, "collator"),
170 }
171 }
172}
173
174pub fn format_number(n: f64) -> String {
176 if n == n.trunc() && n.is_finite() && n.abs() < 1e21 {
177 format!("{}", n as i64)
178 } else {
179 format!("{n}")
180 }
181}