1use std::cell::Cell;
2use std::sync::OnceLock;
3
4use serde::{de, de::DeserializeSeed, Deserialize, Deserializer, Serialize};
5use serde_json::Value;
6
7use crate::error::{Error, Result};
8use crate::json::{canonical_value, parse_strict, StrictSeed};
9use crate::semantics::validate_semantics;
10
11const SCHEMA: &str = include_str!("../assets/raster-style-v2.schema.json");
12static VALIDATOR: OnceLock<std::result::Result<jsonschema::Validator, String>> = OnceLock::new();
13
14pub(crate) fn validator() -> Result<&'static jsonschema::Validator> {
15 VALIDATOR
16 .get_or_init(|| {
17 let schema: Value = serde_json::from_str(SCHEMA).map_err(|error| error.to_string())?;
18 jsonschema::validator_for(&schema).map_err(|error| error.to_string())
19 })
20 .as_ref()
21 .map_err(|_| Error::new("E_CONFIG", "Embedded schema could not be compiled"))
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize)]
26#[serde(transparent)]
27pub struct Style(Value);
28
29impl Style {
30 pub fn from_json(text: &str) -> Result<Self> {
31 Self::from_validated_value(parse_strict(text)?)
32 }
33
34 pub fn from_value(value: Value) -> Result<Self> {
36 Self::from_json(&canonical_value(&value)?)
37 }
38
39 pub(crate) fn from_validated_value(mut value: Value) -> Result<Self> {
40 if !validator()?.is_valid(&value) {
41 return Err(Error::new(
42 "E_SCHEMA",
43 "Raster style does not match the schema",
44 ));
45 }
46 crate::profile::normalize_profile(&mut value)?;
47 validate_semantics(&value)?;
48 normalize_colors(&mut value);
49 canonical_value(&value)?;
50 Ok(Self(value))
51 }
52
53 pub fn as_value(&self) -> &Value {
54 &self.0
55 }
56 pub fn into_value(self) -> Value {
57 self.0
58 }
59 pub fn to_json(&self) -> Result<String> {
60 canonical_value(&self.0)
61 }
62}
63
64impl<'de> Deserialize<'de> for Style {
65 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
66 let nodes = Cell::new(0);
67 let value = StrictSeed::root(&nodes).deserialize(deserializer)?;
68 Self::from_validated_value(value).map_err(de::Error::custom)
69 }
70}
71
72fn normalize_color(value: &mut Value) {
73 if let Some(color) = value.as_str() {
74 let mut normalized = color.to_ascii_lowercase();
75 if normalized.len() == 7 {
76 normalized.push_str("ff");
77 }
78 *value = Value::String(normalized);
79 }
80}
81
82fn normalize_colors(style: &mut Value) {
83 for path in ["/renderer/color", "/image/background"] {
84 if let Some(color) = style.pointer_mut(path) {
85 normalize_color(color);
86 }
87 }
88 let Some(color_map) = style.pointer_mut("/renderer/color_mapping") else {
89 return;
90 };
91 for key in ["under", "over", "outside_color", "fallback_color"] {
92 if let Some(color) = color_map.get_mut(key) {
93 if color.as_str().is_some_and(|color| color.starts_with('#')) {
94 normalize_color(color);
95 }
96 }
97 }
98 for key in ["stops", "entries"] {
99 if let Some(entries) = color_map.get_mut(key).and_then(Value::as_array_mut) {
100 for entry in entries {
101 normalize_color(&mut entry["color"]);
102 }
103 }
104 }
105 if let Some(colors) = color_map.get_mut("colors").and_then(Value::as_array_mut) {
106 for color in colors {
107 normalize_color(color);
108 }
109 }
110}