1use std::cell::Cell;
2
3use serde::{de, de::DeserializeSeed, Deserialize, Deserializer, Serialize};
4use serde_json::Value;
5
6use crate::error::{Error, Result};
7use crate::json::{canonical_value, parse_strict, StrictSeed};
8use crate::schema::{
9 default_schemas, has_extension, SchemaRegistry, ValidationReport, TILE_SCHEMA_URL,
10};
11use crate::semantics::validate_semantics;
12
13#[derive(Debug, Clone, PartialEq, Serialize)]
15#[serde(transparent)]
16pub struct Style {
17 value: Value,
18 #[serde(skip)]
19 validation: ValidationReport,
20}
21
22impl Style {
23 pub fn from_json(text: &str) -> Result<Self> {
29 Self::from_validated_value(parse_strict(text)?)
30 }
31
32 pub fn from_value(value: Value) -> Result<Self> {
37 Self::from_json(&canonical_value(&value)?)
38 }
39
40 pub(crate) fn from_validated_value(value: Value) -> Result<Self> {
41 Self::from_value_in_registry(value, default_schemas()?)
42 }
43
44 pub(crate) fn from_value_in_registry(
45 mut value: Value,
46 schemas: &SchemaRegistry,
47 ) -> Result<Self> {
48 schemas.check(&value)?;
49 crate::profile::normalize_profile(&mut value)?;
50 validate_semantics(&value)?;
51 normalize_colors(&mut value);
52 canonical_value(&value)?;
53 let validation = schemas.check(&value)?;
54 Ok(Self { value, validation })
55 }
56
57 pub fn validation(&self) -> &ValidationReport {
60 &self.validation
61 }
62
63 pub fn require_extension_support(&self, supported: &[&str]) -> Result<()> {
69 if !self.validation.complete {
70 return Err(Error::new(
71 "E_SCHEMA_UNRESOLVED",
72 "Complete validation is required before execution",
73 ));
74 }
75 if let Some(declarations) = self.value["extensions"].as_array() {
76 for declaration in declarations {
77 let url = declaration.as_str().expect("core-validated schema URL");
78 if !supported.contains(&url) {
79 return Err(Error::new(
80 "E_UNSUPPORTED_EXTENSION",
81 "Executor does not support this exact extension version",
82 )
83 .at(url));
84 }
85 }
86 }
87 Ok(())
88 }
89
90 pub fn as_value(&self) -> &Value {
92 &self.value
93 }
94 pub fn into_value(self) -> Value {
96 self.value
97 }
98 pub fn to_json(&self) -> Result<String> {
103 canonical_value(&self.value)
104 }
105}
106
107impl<'de> Deserialize<'de> for Style {
108 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
109 let nodes = Cell::new(0);
110 let value = StrictSeed::root(&nodes).deserialize(deserializer)?;
111 Self::from_validated_value(value).map_err(de::Error::custom)
112 }
113}
114
115fn normalize_color(value: &mut Value) {
116 if let Some(color) = value.as_str() {
117 let mut normalized = color.to_ascii_lowercase();
118 if normalized.len() == 7 {
119 normalized.push_str("ff");
120 }
121 *value = Value::String(normalized);
122 }
123}
124
125fn normalize_colors(style: &mut Value) {
126 let mut paths = vec!["/renderer/color"];
127 if has_extension(style, TILE_SCHEMA_URL) {
128 paths.push("/raster:tile/background");
129 }
130 for path in paths {
131 if let Some(color) = style.pointer_mut(path) {
132 normalize_color(color);
133 }
134 }
135 let Some(color_map) = style.pointer_mut("/renderer/color_mapping") else {
136 return;
137 };
138 for key in ["under", "over", "outside_color", "fallback_color"] {
139 if let Some(color) = color_map.get_mut(key) {
140 if color.as_str().is_some_and(|color| color.starts_with('#')) {
141 normalize_color(color);
142 }
143 }
144 }
145 for key in ["stops", "entries"] {
146 if let Some(entries) = color_map.get_mut(key).and_then(Value::as_array_mut) {
147 for entry in entries {
148 normalize_color(&mut entry["color"]);
149 }
150 }
151 }
152 if let Some(colors) = color_map.get_mut("colors").and_then(Value::as_array_mut) {
153 for color in colors {
154 normalize_color(color);
155 }
156 }
157}