1use serde_json::{json, Value as Json};
27
28pub fn is_function(value: &Json) -> bool {
32 value
33 .as_object()
34 .is_some_and(|o| o.contains_key("stops") || o.contains_key("property"))
35}
36
37pub fn convert_function(params: &Json, spec: &Json) -> Json {
43 let Some(raw_stops) = params.get("stops").and_then(Json::as_array) else {
44 return convert_identity(params, spec);
45 };
46 let zoom_and_feature = raw_stops
47 .first()
48 .and_then(Json::as_array)
49 .and_then(|s| s.first())
50 .map(Json::is_object)
51 .unwrap_or(false);
52 let feature_dependent = zoom_and_feature || params.get("property").is_some();
53 let zoom_dependent = zoom_and_feature || !feature_dependent;
54 let tokens = spec.get("tokens").and_then(Json::as_bool).unwrap_or(false);
55
56 let stops: Vec<(Json, Json)> = raw_stops
57 .iter()
58 .filter_map(Json::as_array)
59 .filter(|s| s.len() >= 2)
60 .map(|s| {
61 let output = if !feature_dependent && tokens && s[1].is_string() {
62 convert_token_string(s[1].as_str().unwrap())
63 } else {
64 convert_literal(&s[1])
65 };
66 (s[0].clone(), output)
67 })
68 .collect();
69
70 if zoom_and_feature {
71 convert_zoom_and_property(params, spec, &stops)
72 } else if zoom_dependent {
73 convert_zoom(params, spec, &stops, json!(["zoom"]))
74 } else {
75 convert_property(params, spec, &stops)
76 }
77}
78
79fn convert_literal(v: &Json) -> Json {
80 if v.is_object() || v.is_array() {
81 json!(["literal", v])
82 } else {
83 v.clone()
84 }
85}
86
87fn function_type(params: &Json, spec: &Json) -> String {
88 if let Some(t) = params.get("type").and_then(Json::as_str) {
89 return t.to_string();
90 }
91 let interpolated = spec
92 .get("expression")
93 .and_then(|e| e.get("interpolated"))
94 .and_then(Json::as_bool)
95 .unwrap_or(false);
96 if interpolated {
97 "exponential"
98 } else {
99 "interval"
100 }
101 .to_string()
102}
103
104fn interpolate_operator(params: &Json) -> &'static str {
105 match params.get("colorSpace").and_then(Json::as_str) {
106 Some("hcl") => "interpolate-hcl",
107 Some("lab") => "interpolate-lab",
108 _ => "interpolate",
109 }
110}
111
112fn get_fallback(params: &Json, spec: &Json) -> Json {
113 let d = params
114 .get("default")
115 .or_else(|| spec.get("default"))
116 .cloned();
117 match d {
118 Some(v) => convert_literal(&v),
119 None => Json::Null,
120 }
121}
122
123fn append_stop_pair(curve: &mut Vec<Json>, input: Json, output: Json, is_step: bool) {
124 if curve.len() > 3 && curve.get(curve.len() - 2) == Some(&input) {
125 return;
126 }
127 if !(is_step && curve.len() == 2) {
128 curve.push(input);
129 }
130 curve.push(output);
131}
132
133fn fixup_degenerate_step(curve: &mut Vec<Json>) {
134 if curve.first().and_then(Json::as_str) == Some("step") && curve.len() == 3 {
135 let out = curve[2].clone();
136 curve.push(json!(0));
137 curve.push(out);
138 }
139}
140
141fn convert_token_string(s: &str) -> Json {
142 let mut result: Vec<Json> = vec![json!("concat")];
144 let bytes = s.as_bytes();
145 let mut pos = 0;
146 let mut i = 0;
147 while i < bytes.len() {
148 if bytes[i] == b'{' {
149 if let Some(end) = s[i..].find('}') {
150 let close = i + end;
151 if i > pos {
152 result.push(json!(&s[pos..i]));
153 }
154 result.push(json!(["get", &s[i + 1..close]]));
155 i = close + 1;
156 pos = i;
157 continue;
158 }
159 }
160 i += 1;
161 }
162 if result.len() == 1 {
163 return json!(s);
164 }
165 if pos < s.len() {
166 result.push(json!(&s[pos..]));
167 } else if result.len() == 2 {
168 return json!(["to-string", result[1]]);
169 }
170 Json::Array(result)
171}
172
173fn convert_identity(params: &Json, spec: &Json) -> Json {
174 let get = json!(["get", params.get("property")]);
175 let spec_type = spec.get("type").and_then(Json::as_str).unwrap_or("");
176 if params.get("default").is_none() {
177 return if spec_type == "string" {
178 json!(["string", get])
179 } else {
180 get
181 };
182 }
183 if spec_type == "enum" {
184 let keys: Vec<Json> = spec
185 .get("values")
186 .and_then(Json::as_object)
187 .map(|o| o.keys().map(|k| json!(k)).collect())
188 .unwrap_or_default();
189 return json!(["match", get, keys, get, params.get("default")]);
190 }
191 let op = if spec_type == "color" {
192 "to-color"
193 } else {
194 spec_type
195 };
196 let mut expr = vec![json!(op)];
197 if spec_type == "array" {
198 expr.push(spec.get("value").cloned().unwrap_or(Json::Null));
199 expr.push(spec.get("length").cloned().unwrap_or(Json::Null));
200 }
201 expr.push(get);
202 expr.push(convert_literal(params.get("default").unwrap()));
203 Json::Array(expr)
204}
205
206fn convert_property(params: &Json, spec: &Json, stops: &[(Json, Json)]) -> Json {
207 let ty = function_type(params, spec);
208 let get = json!(["get", params.get("property")]);
209 match ty.as_str() {
210 "categorical" if stops.first().map(|s| s.0.is_boolean()).unwrap_or(false) => {
211 let mut expr = vec![json!("case")];
212 for (input, output) in stops {
213 expr.push(json!(["==", get, input]));
214 expr.push(output.clone());
215 }
216 expr.push(get_fallback(params, spec));
217 Json::Array(expr)
218 }
219 "categorical" => {
220 let mut expr = vec![json!("match"), get];
221 for (input, output) in stops {
222 append_stop_pair(&mut expr, input.clone(), output.clone(), false);
223 }
224 expr.push(get_fallback(params, spec));
225 Json::Array(expr)
226 }
227 "interval" => {
228 let mut expr = vec![json!("step"), json!(["number", get])];
229 for (input, output) in stops {
230 append_stop_pair(&mut expr, input.clone(), output.clone(), true);
231 }
232 fixup_degenerate_step(&mut expr);
233 wrap_default(params, Json::Array(expr), &get)
234 }
235 _ => {
236 let base = params.get("base").and_then(Json::as_f64).unwrap_or(1.0);
238 let interp = if base == 1.0 {
239 json!(["linear"])
240 } else {
241 json!(["exponential", base])
242 };
243 let mut expr = vec![
244 json!(interpolate_operator(params)),
245 interp,
246 json!(["number", get]),
247 ];
248 for (input, output) in stops {
249 append_stop_pair(&mut expr, input.clone(), output.clone(), false);
250 }
251 wrap_default(params, Json::Array(expr), &get)
252 }
253 }
254}
255
256fn wrap_default(params: &Json, expr: Json, get: &Json) -> Json {
259 match params.get("default") {
260 None => expr,
261 Some(default) => json!([
262 "case",
263 ["==", ["typeof", get], "number"],
264 expr,
265 convert_literal(default)
266 ]),
267 }
268}
269
270fn convert_zoom(params: &Json, spec: &Json, stops: &[(Json, Json)], input: Json) -> Json {
271 let ty = function_type(params, spec);
272 let (mut expr, is_step) = if ty == "interval" {
273 (vec![json!("step"), input], true)
274 } else {
275 let base = params.get("base").and_then(Json::as_f64).unwrap_or(1.0);
276 let interp = if base == 1.0 {
277 json!(["linear"])
278 } else {
279 json!(["exponential", base])
280 };
281 (
282 vec![json!(interpolate_operator(params)), interp, input],
283 false,
284 )
285 };
286 for (i, o) in stops {
287 append_stop_pair(&mut expr, i.clone(), o.clone(), is_step);
288 }
289 fixup_degenerate_step(&mut expr);
290 Json::Array(expr)
291}
292
293fn convert_zoom_and_property(params: &Json, spec: &Json, stops: &[(Json, Json)]) -> Json {
294 let mut zooms: Vec<f64> = Vec::new();
296 let mut grouped: Vec<Vec<(Json, Json)>> = Vec::new();
297 for (key, output) in stops {
298 let zoom = key.get("zoom").and_then(Json::as_f64).unwrap_or(0.0);
299 let value = key.get("value").cloned().unwrap_or(Json::Null);
300 match zooms.iter().position(|z| *z == zoom) {
301 Some(idx) => grouped[idx].push((value, output.clone())),
302 None => {
303 zooms.push(zoom);
304 grouped.push(vec![(value, output.clone())]);
305 }
306 }
307 }
308 let feature_params = |zoom: f64| {
309 let mut m = serde_json::Map::new();
310 m.insert("zoom".into(), json!(zoom));
311 for key in ["type", "property", "default"] {
312 if let Some(v) = params.get(key) {
313 m.insert(key.into(), v.clone());
314 }
315 }
316 Json::Object(m)
317 };
318 let ty = function_type(&json!({}), spec);
319 if ty == "exponential" {
320 let mut expr = vec![
321 json!(interpolate_operator(params)),
322 json!(["linear"]),
323 json!(["zoom"]),
324 ];
325 for (i, z) in zooms.iter().enumerate() {
326 let output = convert_property(&feature_params(*z), spec, &grouped[i]);
327 append_stop_pair(&mut expr, json!(z), output, false);
328 }
329 Json::Array(expr)
330 } else {
331 let mut expr = vec![json!("step"), json!(["zoom"])];
332 for (i, z) in zooms.iter().enumerate() {
333 let output = convert_property(&feature_params(*z), spec, &grouped[i]);
334 append_stop_pair(&mut expr, json!(z), output, true);
335 }
336 fixup_degenerate_step(&mut expr);
337 Json::Array(expr)
338 }
339}