1use crate::diagnostic::Diagnostics;
8use crate::{
9 Code, Diagnostic, FieldValue,
10 diagnostic::Positions,
11 files::Source,
12 schema::{References, Target},
13};
14use std::{
15 cell::{OnceCell, RefCell},
16 collections::BTreeSet,
17};
18use tilewright_jsonc::{MAX_SAFE_INTEGER, Member, Span, Value};
19
20#[must_use]
22pub fn is_identifier(text: &str) -> bool {
23 let bytes = text.as_bytes();
24 !bytes.is_empty()
25 && bytes.len() <= 128
26 && (bytes[0].is_ascii_lowercase() || bytes[0].is_ascii_digit())
27 && bytes
28 .iter()
29 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"_.-".contains(byte))
30}
31
32#[must_use]
35pub fn is_namespace(text: &str) -> bool {
36 let (domain, rest) = text.split_once('/').unwrap_or((text, ""));
37 domain.contains('.')
38 && is_identifier(domain)
39 && !domain.starts_with('.')
40 && !domain.ends_with('.')
41 && (rest.is_empty() || is_identifier(rest))
42}
43
44pub(crate) struct Reader<'a> {
47 pub source: &'a Source,
48 diagnostics: RefCell<&'a mut Diagnostics>,
49 positions: OnceCell<Positions<'a>>,
51}
52
53impl<'a> Reader<'a> {
54 pub fn new(source: &'a Source, diagnostics: &'a mut Diagnostics) -> Self {
55 Self {
56 source,
57 diagnostics: RefCell::new(diagnostics),
58 positions: OnceCell::new(),
59 }
60 }
61
62 fn positions(&self) -> &Positions<'a> {
63 self.positions
64 .get_or_init(|| Positions::new(self.source.document.source()))
65 }
66
67 pub fn reference(&self, references: &References, target: &Target, span: Span) {
69 if !references.resolves(target) {
70 self.report(span, Code::Reference, target.message());
71 }
72 }
73
74 pub fn root(&self) -> &'a Value {
75 self.source.document.root()
76 }
77
78 pub fn canonical(&self, value: &Value) -> String {
79 self.source.document.canonical(value)
80 }
81
82 pub fn diagnostic(&self, span: Span, code: Code, message: impl Into<String>) -> Diagnostic {
84 Diagnostic::at(
85 &self.source.path,
86 self.positions(),
87 span.start,
88 span.end,
89 code,
90 message,
91 )
92 }
93
94 pub fn report(&self, span: Span, code: Code, message: impl Into<String>) {
95 let diagnostic = self.diagnostic(span, code, message);
96 self.diagnostics.borrow_mut().push(diagnostic);
97 }
98
99 pub fn object(&self, value: &'a Value, allowed: &[&str]) -> Option<&'a Value> {
101 let Some(members) = value.as_object() else {
102 self.report(
103 value.span(),
104 Code::Type,
105 format!("expected an object, found {}", value.kind()),
106 );
107 return None;
108 };
109 for member in members {
110 if !allowed.contains(&member.key.as_str()) {
111 self.report(
112 member.key_span,
113 Code::UnknownKey,
114 format!("unknown key \"{}\"", member.key),
115 );
116 }
117 }
118 Some(value)
119 }
120
121 pub fn members(&self, value: &'a Value) -> Option<&'a [Member]> {
123 let members = value.as_object();
124 if members.is_none() {
125 self.report(
126 value.span(),
127 Code::Type,
128 format!("expected an object, found {}", value.kind()),
129 );
130 }
131 members
132 }
133
134 pub fn required(&self, object: &'a Value, key: &str) -> Option<&'a Value> {
135 let value = object.get(key);
136 if value.is_none() {
137 self.report(
138 object.span(),
139 Code::MissingKey,
140 format!("missing key \"{key}\""),
141 );
142 }
143 value
144 }
145
146 pub fn format(&self, root: &'a Value, expected: &str) -> bool {
148 match self.required(root, "format") {
149 Some(value) if value.as_str() == Some(expected) => true,
150 Some(value) => {
151 self.report(
152 value.span(),
153 Code::Format,
154 format!("format must be \"{expected}\""),
155 );
156 false
157 }
158 None => false,
159 }
160 }
161
162 pub fn string(&self, value: &'a Value) -> Option<&'a str> {
163 let text = value.as_str();
164 if text.is_none() {
165 self.report(
166 value.span(),
167 Code::Type,
168 format!("expected a string, found {}", value.kind()),
169 );
170 }
171 text
172 }
173
174 pub fn text(&self, value: &'a Value, maximum: usize) -> Option<String> {
176 let text = self.string(value)?;
177 if text.chars().count() > maximum {
178 self.report(
179 value.span(),
180 Code::Range,
181 format!("longer than {maximum} characters"),
182 );
183 return None;
184 }
185 Some(text.to_owned())
186 }
187
188 pub fn identifier(&self, value: &'a Value) -> Option<String> {
189 let text = self.string(value)?;
190 if is_identifier(text) {
191 Some(text.to_owned())
192 } else {
193 self.report(
194 value.span(),
195 Code::Identifier,
196 format!("\"{text}\" is not a valid identifier"),
197 );
198 None
199 }
200 }
201
202 pub fn boolean(&self, value: &'a Value) -> Option<bool> {
203 let flag = value.as_bool();
204 if flag.is_none() {
205 self.report(
206 value.span(),
207 Code::Type,
208 format!("expected a boolean, found {}", value.kind()),
209 );
210 }
211 flag
212 }
213
214 pub fn integer(&self, value: &'a Value, minimum: i64, maximum: i64) -> Option<i64> {
216 let Some(number) = value.as_i64() else {
217 self.report(
218 value.span(),
219 Code::Type,
220 format!("expected an integer, found {}", value.kind()),
221 );
222 return None;
223 };
224 if (minimum..=maximum).contains(&number) {
225 Some(number)
226 } else {
227 self.report(
228 value.span(),
229 Code::Range,
230 format!("{number} is outside {minimum}..={maximum}"),
231 );
232 None
233 }
234 }
235
236 pub fn i32(&self, value: &'a Value) -> Option<i32> {
237 self.integer(value, i64::from(i32::MIN), i64::from(i32::MAX))
238 .and_then(|number| i32::try_from(number).ok())
239 }
240
241 pub fn u32(&self, value: &'a Value, minimum: u32) -> Option<u32> {
242 self.integer(value, i64::from(minimum), i64::from(u32::MAX))
243 .and_then(|number| u32::try_from(number).ok())
244 }
245
246 pub fn array(&self, value: &'a Value) -> Option<&'a [Value]> {
247 let items = value.as_array();
248 if items.is_none() {
249 self.report(
250 value.span(),
251 Code::Type,
252 format!("expected an array, found {}", value.kind()),
253 );
254 }
255 items
256 }
257
258 pub fn coordinates(&self, value: &'a Value) -> Option<[i64; 2]> {
260 if let [x, y] = self.array(value)? {
261 let x = self.integer(x, -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER);
263 let y = self.integer(y, -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER);
264 Some([x?, y?])
265 } else {
266 self.report(value.span(), Code::Type, "expected [x, y]");
267 None
268 }
269 }
270
271 pub fn identifiers(&self, value: &'a Value) -> Option<Vec<String>> {
273 let items = self.array(value)?;
274 let mut seen = BTreeSet::new();
275 let mut output = Vec::with_capacity(items.len());
276 for item in items {
277 let identifier = self.identifier(item)?;
278 if !seen.insert(identifier.clone()) {
279 self.report(
280 item.span(),
281 Code::Duplicate,
282 format!("\"{identifier}\" is listed twice"),
283 );
284 return None;
285 }
286 output.push(identifier);
287 }
288 Some(output)
289 }
290
291 pub fn custom(&self, object: &'a Value) -> Option<String> {
293 let value = object.get("custom")?;
294 let members = value.as_object();
295 if members.is_none() {
296 self.report(value.span(), Code::Type, "custom must be an object");
297 return None;
298 }
299 for member in members.unwrap_or_default() {
300 if !is_namespace(&member.key) {
301 self.report(
302 member.key_span,
303 Code::Identifier,
304 format!(
305 "custom key \"{}\" must be a domain-qualified namespace",
306 member.key
307 ),
308 );
309 }
310 }
311 Some(self.canonical(value))
312 }
313
314 pub fn field_value(&self, value: &Value) -> FieldValue {
316 match value {
317 Value::Null { .. } => FieldValue::Null,
318 Value::Bool { value, .. } => FieldValue::Bool(*value),
319 Value::Integer { value, .. } => FieldValue::Integer(*value),
320 Value::Float { value, .. } => FieldValue::Number(*value),
321 Value::String { value, .. } => FieldValue::String(value.clone()),
322 Value::Array { .. } | Value::Object { .. } => FieldValue::Json(self.canonical(value)),
323 }
324 }
325}