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