1use std::collections::HashSet;
13use std::fmt;
14use std::sync::{Arc, Weak};
15
16use serde_json::{Map, Number, Value};
17
18use crate::compose::{ComposeErrorCode, ComposeLoadError};
19use crate::validate::{FormInputError, ValidateError, ValidateOptions, ValidationResult};
20use crate::{ValidateDetailOptions, ValidateListOptions};
21
22pub const MESSAGE: &str = "Text must be Unicode scalar values";
24
25pub const VALUE_LIMIT_MESSAGE: &str = "Recursive or excessively nested value";
27
28const MAX_DEPTH: usize = 512;
30
31pub(crate) const NODE_LIMIT: usize = 1_000_000;
33
34pub(crate) const NESTING_LIMIT: usize = 512;
36
37#[derive(Clone, Debug, PartialEq, Eq, Hash)]
40pub enum JsonString {
41 Text(String),
43 Units(Vec<u16>),
45}
46
47impl JsonString {
48 pub fn as_str(&self) -> Option<&str> {
50 match self {
51 JsonString::Text(text) => Some(text),
52 JsonString::Units(_) => None,
53 }
54 }
55}
56
57#[derive(Clone, Debug)]
65pub enum JsonText {
66 Null,
68 Bool(bool),
70 Number(String),
72 String(JsonString),
74 Array(Vec<JsonText>),
76 Object(Vec<(JsonString, JsonText)>),
78 Shared(Arc<JsonText>),
80 Link(Weak<JsonText>),
82}
83
84impl PartialEq for JsonText {
85 fn eq(&self, other: &Self) -> bool {
86 match (self, other) {
87 (JsonText::Null, JsonText::Null) => true,
88 (JsonText::Bool(a), JsonText::Bool(b)) => a == b,
89 (JsonText::Number(a), JsonText::Number(b)) => a == b,
90 (JsonText::String(a), JsonText::String(b)) => a == b,
91 (JsonText::Array(a), JsonText::Array(b)) => a == b,
92 (JsonText::Object(a), JsonText::Object(b)) => a == b,
93 (JsonText::Shared(a), JsonText::Shared(b)) => Arc::ptr_eq(a, b) || a == b,
94 (JsonText::Link(a), JsonText::Link(b)) => Weak::ptr_eq(a, b),
95 _ => false,
96 }
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct JsonTextError {
103 pub offset: usize,
105}
106
107impl fmt::Display for JsonTextError {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(f, "Invalid JSON text at byte {}", self.offset)
110 }
111}
112
113impl std::error::Error for JsonTextError {}
114
115struct Parser<'a> {
116 bytes: &'a [u8],
117 pos: usize,
118}
119
120impl Parser<'_> {
121 fn error<T>(&self) -> Result<T, JsonTextError> {
122 Err(JsonTextError { offset: self.pos })
123 }
124
125 fn space(&mut self) {
126 while matches!(self.bytes.get(self.pos), Some(b' ' | b'\t' | b'\n' | b'\r')) {
127 self.pos += 1;
128 }
129 }
130
131 fn literal(&mut self, word: &[u8], value: JsonText) -> Result<JsonText, JsonTextError> {
132 if self.bytes[self.pos..].starts_with(word) {
133 self.pos += word.len();
134 Ok(value)
135 } else {
136 self.error()
137 }
138 }
139
140 fn value(&mut self, depth: usize) -> Result<JsonText, JsonTextError> {
141 self.space();
142 match self.bytes.get(self.pos) {
143 Some(b'{') => {
144 if depth >= MAX_DEPTH {
145 return self.error();
146 }
147 self.pos += 1;
148 let mut members: Vec<(JsonString, JsonText)> = Vec::new();
149 let mut names: HashSet<JsonString> = HashSet::new();
150 self.space();
151 if self.bytes.get(self.pos) == Some(&b'}') {
152 self.pos += 1;
153 return Ok(JsonText::Object(members));
154 }
155 loop {
156 self.space();
157 if self.bytes.get(self.pos) != Some(&b'"') {
158 return self.error();
159 }
160 let offset = self.pos;
161 let name = self.string()?;
162 if !names.insert(name.clone()) {
163 return Err(JsonTextError { offset });
164 }
165 self.space();
166 if self.bytes.get(self.pos) != Some(&b':') {
167 return self.error();
168 }
169 self.pos += 1;
170 let value = self.value(depth + 1)?;
171 members.push((name, value));
172 self.space();
173 match self.bytes.get(self.pos) {
174 Some(b',') => self.pos += 1,
175 Some(b'}') => {
176 self.pos += 1;
177 return Ok(JsonText::Object(members));
178 }
179 _ => return self.error(),
180 }
181 }
182 }
183 Some(b'[') => {
184 if depth >= MAX_DEPTH {
185 return self.error();
186 }
187 self.pos += 1;
188 let mut items = Vec::new();
189 self.space();
190 if self.bytes.get(self.pos) == Some(&b']') {
191 self.pos += 1;
192 return Ok(JsonText::Array(items));
193 }
194 loop {
195 items.push(self.value(depth + 1)?);
196 self.space();
197 match self.bytes.get(self.pos) {
198 Some(b',') => self.pos += 1,
199 Some(b']') => {
200 self.pos += 1;
201 return Ok(JsonText::Array(items));
202 }
203 _ => return self.error(),
204 }
205 }
206 }
207 Some(b'"') => Ok(JsonText::String(self.string()?)),
208 Some(b't') => self.literal(b"true", JsonText::Bool(true)),
209 Some(b'f') => self.literal(b"false", JsonText::Bool(false)),
210 Some(b'n') => self.literal(b"null", JsonText::Null),
211 Some(b'-' | b'0'..=b'9') => self.number(),
212 _ => self.error(),
213 }
214 }
215
216 fn digits(&mut self) -> usize {
217 let start = self.pos;
218 while matches!(self.bytes.get(self.pos), Some(b'0'..=b'9')) {
219 self.pos += 1;
220 }
221 self.pos - start
222 }
223
224 fn number(&mut self) -> Result<JsonText, JsonTextError> {
225 let start = self.pos;
226 if self.bytes[self.pos] == b'-' {
227 self.pos += 1;
228 }
229 match self.bytes.get(self.pos) {
230 Some(b'0') => self.pos += 1,
231 Some(b'1'..=b'9') => {
232 self.digits();
233 }
234 _ => return self.error(),
235 }
236 if self.bytes.get(self.pos) == Some(&b'.') {
237 self.pos += 1;
238 if self.digits() == 0 {
239 return self.error();
240 }
241 }
242 if matches!(self.bytes.get(self.pos), Some(b'e' | b'E')) {
243 self.pos += 1;
244 if matches!(self.bytes.get(self.pos), Some(b'+' | b'-')) {
245 self.pos += 1;
246 }
247 if self.digits() == 0 {
248 return self.error();
249 }
250 }
251 let text = std::str::from_utf8(&self.bytes[start..self.pos]).expect("ASCII digits");
252 Ok(JsonText::Number(text.to_owned()))
253 }
254
255 fn hex(&mut self) -> Result<u16, JsonTextError> {
256 let digits = self
257 .bytes
258 .get(self.pos..self.pos + 4)
259 .ok_or(JsonTextError { offset: self.pos })?;
260 if !digits.iter().all(u8::is_ascii_hexdigit) {
261 return self.error();
262 }
263 let text = std::str::from_utf8(digits).expect("ASCII hex digits are UTF-8");
265 self.pos += 4;
266 Ok(u16::from_str_radix(text, 16).expect("hex digits"))
267 }
268
269 fn string(&mut self) -> Result<JsonString, JsonTextError> {
270 self.pos += 1;
271 let mut units: Vec<u16> = Vec::new();
272 loop {
273 let Some(&byte) = self.bytes.get(self.pos) else {
274 return self.error();
275 };
276 match byte {
277 b'"' => {
278 self.pos += 1;
279 return Ok(match String::from_utf16(&units) {
280 Ok(text) => JsonString::Text(text),
281 Err(_) => JsonString::Units(units),
282 });
283 }
284 b'\\' => {
285 self.pos += 1;
286 let unit = match self.bytes.get(self.pos) {
287 Some(b'"') => u16::from(b'"'),
288 Some(b'\\') => u16::from(b'\\'),
289 Some(b'/') => u16::from(b'/'),
290 Some(b'b') => 0x08,
291 Some(b'f') => 0x0c,
292 Some(b'n') => u16::from(b'\n'),
293 Some(b'r') => u16::from(b'\r'),
294 Some(b't') => u16::from(b'\t'),
295 Some(b'u') => {
296 self.pos += 1;
297 units.push(self.hex()?);
298 continue;
299 }
300 _ => return self.error(),
301 };
302 self.pos += 1;
303 units.push(unit);
304 }
305 0x00..=0x1f => return self.error(),
306 _ => {
307 let length = match byte {
309 0x00..=0x7f => 1,
310 0xc0..=0xdf => 2,
311 0xe0..=0xef => 3,
312 _ => 4,
313 };
314 let character = std::str::from_utf8(&self.bytes[self.pos..self.pos + length])
315 .expect("the input is a str, so each character is whole")
316 .chars()
317 .next()
318 .ok_or(JsonTextError { offset: self.pos })?;
319 let mut buffer = [0u16; 2];
320 units.extend_from_slice(character.encode_utf16(&mut buffer));
321 self.pos += character.len_utf8();
322 }
323 }
324 }
325 }
326}
327
328impl JsonText {
329 pub fn parse(text: &str) -> Result<JsonText, JsonTextError> {
332 let mut parser = Parser {
333 bytes: text.as_bytes(),
334 pos: 0,
335 };
336 let value = parser.value(0)?;
337 parser.space();
338 if parser.pos != parser.bytes.len() {
339 return parser.error();
340 }
341 Ok(value)
342 }
343
344 pub fn get(&self, name: &str) -> Option<&JsonText> {
346 match self {
347 JsonText::Object(members) => members
348 .iter()
349 .find(|(member, _)| member.as_str() == Some(name))
350 .map(|(_, value)| value),
351 JsonText::Shared(target) => target.get(name),
352 _ => None,
353 }
354 }
355
356 pub fn item(&self, index: usize) -> Option<&JsonText> {
358 match self {
359 JsonText::Array(items) => items.get(index),
360 JsonText::Shared(target) => target.item(index),
361 _ => None,
362 }
363 }
364
365 pub fn to_value(&self) -> Option<Value> {
368 if value_failure(self) == Some(ValueFailure::Limit) {
369 return None;
370 }
371 json_text_to_value(self)
372 }
373
374 pub fn shape(&self) -> Value {
381 if value_failure(self) == Some(ValueFailure::Limit) {
382 return Value::Null;
383 }
384 shape_of(self)
385 }
386
387 pub fn has_invalid_text(&self) -> bool {
389 matches!(value_failure(self), Some(ValueFailure::Text(_)))
390 }
391
392 pub fn invalid_path(&self) -> Option<Vec<String>> {
396 match value_failure(self) {
397 Some(ValueFailure::Text(path)) => Some(path),
398 _ => None,
399 }
400 }
401
402 pub fn has_limit_violation(&self) -> bool {
404 value_failure(self) == Some(ValueFailure::Limit)
405 }
406}
407
408#[derive(Clone, Debug, PartialEq, Eq)]
410pub(crate) enum ValueFailure {
411 Text(Vec<String>),
413 Limit,
415}
416
417#[derive(Default)]
419struct Walk {
420 nodes: usize,
421}
422
423impl Walk {
424 fn admit(&mut self, node: &JsonText, depth: usize) -> bool {
426 self.nodes += 1;
427 if self.nodes > NODE_LIMIT {
428 return false;
429 }
430 depth < NESTING_LIMIT || !matches!(node, JsonText::Array(_) | JsonText::Object(_))
431 }
432}
433
434fn with_node<R>(value: &JsonText, f: impl FnOnce(&JsonText) -> R) -> R {
437 match value {
438 JsonText::Shared(target) => with_node(target, f),
439 JsonText::Link(target) => {
440 let target = target
441 .upgrade()
442 .expect("a link refers to a shared value that its owner holds");
443 with_node(&target, f)
444 }
445 other => f(other),
446 }
447}
448
449fn holds_failure(value: &JsonText, depth: usize, walk: &mut Walk) -> bool {
452 with_node(value, |node| {
453 if !walk.admit(node, depth) {
454 return true;
455 }
456 match node {
457 JsonText::String(text) => text.as_str().is_none(),
458 JsonText::Array(items) => items
459 .iter()
460 .any(|item| holds_failure(item, depth + 1, walk)),
461 JsonText::Object(members) => members.iter().any(|(name, member)| {
462 name.as_str().is_none() || holds_failure(member, depth + 1, walk)
463 }),
464 _ => false,
465 }
466 })
467}
468
469fn first_failure(
472 value: &JsonText,
473 path: &mut Vec<String>,
474 depth: usize,
475 walk: &mut Walk,
476) -> Option<ValueFailure> {
477 with_node(value, |node| {
478 if !walk.admit(node, depth) {
479 return Some(ValueFailure::Limit);
480 }
481 match node {
482 JsonText::String(text) => text
483 .as_str()
484 .is_none()
485 .then(|| ValueFailure::Text(path.clone())),
486 JsonText::Array(items) => {
487 for (index, item) in items.iter().enumerate() {
488 path.push(index.to_string());
489 let found = first_failure(item, path, depth + 1, walk);
490 path.pop();
491 if found.is_some() {
492 return found;
493 }
494 }
495 None
496 }
497 JsonText::Object(members) => {
498 let mut named = Vec::with_capacity(members.len());
499 for (name, member) in members {
500 match name.as_str() {
501 Some(text) => named.push((text, member)),
502 None => return Some(ValueFailure::Text(path.clone())),
503 }
504 }
505 named.sort_by(|a, b| a.0.cmp(b.0));
507 for (name, member) in named {
508 path.push(name.to_owned());
509 let found = first_failure(member, path, depth + 1, walk);
510 path.pop();
511 if found.is_some() {
512 return found;
513 }
514 }
515 None
516 }
517 _ => None,
518 }
519 })
520}
521
522fn value_failure(value: &JsonText) -> Option<ValueFailure> {
525 if !holds_failure(value, 0, &mut Walk::default()) {
526 return None;
527 }
528 Some(
529 first_failure(value, &mut Vec::new(), 0, &mut Walk::default())
530 .expect("the ordered walk reports the failure that the quick walk found"),
531 )
532}
533
534fn shape_of(value: &JsonText) -> Value {
536 with_node(value, |node| match node {
537 JsonText::Array(items) => Value::Array(items.iter().map(shape_of).collect()),
538 JsonText::Object(members) => {
539 let mut map = Map::new();
540 for (index, (name, member)) in members.iter().enumerate() {
541 let name = match name {
542 JsonString::Text(text) => text.clone(),
543 JsonString::Units(_) => format!("\u{FFFF}{index}"),
544 };
545 map.insert(name, shape_of(member));
546 }
547 Value::Object(map)
548 }
549 JsonText::String(JsonString::Units(_)) => Value::String(String::new()),
550 JsonText::String(JsonString::Text(text)) => Value::String(text.clone()),
551 JsonText::Null => Value::Null,
552 JsonText::Bool(value) => Value::Bool(*value),
553 JsonText::Number(text) => number_value(text).unwrap_or(Value::Null),
554 JsonText::Shared(_) | JsonText::Link(_) => unreachable!("with_node resolves shared values"),
555 })
556}
557
558pub fn check_specification(
565 spec: Option<&JsonText>,
566 files: Option<&JsonText>,
567) -> Result<(), ValidateError> {
568 for (name, value) in [("spec", spec), ("files", files)] {
569 if let Some(value) = value {
570 match value_failure(value) {
571 Some(ValueFailure::Limit) => {
572 return Err(ValidateError::Input(FormInputError::new(format!(
573 "{VALUE_LIMIT_MESSAGE}: {name}"
574 ))));
575 }
576 Some(ValueFailure::Text(trace)) => {
577 return Err(ValidateError::Load(ComposeLoadError::with_trace(
578 ComposeErrorCode::InvalidText,
579 MESSAGE,
580 trace,
581 )));
582 }
583 None => {}
584 }
585 }
586 }
587 Ok(())
588}
589
590pub fn check_inputs(inputs: &[(&str, Option<&JsonText>)]) -> Result<(), FormInputError> {
594 for (name, value) in inputs {
595 if let Some(value) = value {
596 match value_failure(value) {
597 Some(ValueFailure::Limit) => {
598 return Err(FormInputError::new(format!(
599 "{VALUE_LIMIT_MESSAGE}: {name}"
600 )));
601 }
602 Some(ValueFailure::Text(path)) => {
603 let mut location = vec![(*name).to_owned()];
604 location.extend(path);
605 return Err(FormInputError::new(format!(
606 "{MESSAGE}: {}",
607 location.join(".")
608 )));
609 }
610 None => {}
611 }
612 }
613 }
614 Ok(())
615}
616
617#[allow(clippy::disallowed_methods, reason = "a number serde_json rejects is invalid")]
620fn number_value(text: &str) -> Option<Value> {
621 serde_json::from_str::<Number>(text).ok().map(Value::Number)
622}
623
624fn json_text_to_value(value: &JsonText) -> Option<Value> {
628 with_node(value, |node| match node {
629 JsonText::Null => Some(Value::Null),
630 JsonText::Bool(b) => Some(Value::Bool(*b)),
631 JsonText::Number(text) => number_value(text),
632 JsonText::String(text) => Some(Value::String(text.as_str()?.to_owned())),
633 JsonText::Array(items) => items
634 .iter()
635 .map(json_text_to_value)
636 .collect::<Option<Vec<_>>>()
637 .map(Value::Array),
638 JsonText::Object(members) => {
639 let mut names = Vec::with_capacity(members.len());
641 for (name, _) in members {
642 names.push(name.as_str()?);
643 }
644 let mut object = Map::new();
645 for (name, (_, member)) in names.into_iter().zip(members) {
646 object.insert(name.to_owned(), json_text_to_value(member)?);
647 }
648 Some(Value::Object(object))
649 }
650 JsonText::Shared(_) | JsonText::Link(_) => unreachable!("with_node resolves shared values"),
651 })
652}
653
654fn decoded(value: Option<&JsonText>) -> Result<Option<Value>, ValidateError> {
657 value
658 .map(|value| {
659 json_text_to_value(value).ok_or_else(|| {
660 ValidateError::Input(FormInputError::new("Numbers must be finite JSON numbers"))
661 })
662 })
663 .transpose()
664}
665
666type Composition = (Option<Map<String, Value>>, Option<String>);
668
669fn composition(
671 files: Option<&JsonText>,
672 basepath: Option<&JsonText>,
673) -> Result<Composition, ValidateError> {
674 let files = decoded(files)?.and_then(|files| match files {
675 Value::Object(files) => Some(files),
676 _ => None,
677 });
678 let basepath = decoded(basepath)?.and_then(|basepath| basepath.as_str().map(str::to_owned));
679 Ok((files, basepath))
680}
681
682pub fn validate_text(
687 spec: &JsonText,
688 data: Option<&JsonText>,
689 files: Option<&JsonText>,
690 basepath: Option<&JsonText>,
691) -> Result<ValidationResult, ValidateError> {
692 check_specification(Some(spec), files)?;
693 check_inputs(&[("data", data), ("options.basepath", basepath)])?;
694 let (files, basepath) = composition(files, basepath)?;
695 let spec = decoded(Some(spec))?.unwrap_or(Value::Null);
696 let data = decoded(data)?.unwrap_or_else(|| Value::Object(Map::new()));
697 crate::validate(
698 &spec,
699 &data,
700 &ValidateOptions {
701 files,
702 loader: None,
703 basepath,
704 },
705 )
706}
707
708fn structure_text(
711 spec: &JsonText,
712 files: Option<&JsonText>,
713 basepath: Option<&JsonText>,
714 validate: impl FnOnce(&Value, Option<Map<String, Value>>, Option<String>) -> Result<(), ValidateError>,
715) -> Result<(), ValidateError> {
716 check_specification(Some(spec), files)?;
717 check_inputs(&[("options.basepath", basepath)])?;
718 let (files, basepath) = composition(files, basepath)?;
719 let spec = decoded(Some(spec))?.unwrap_or(Value::Null);
720 validate(&spec, files, basepath)
721}
722
723macro_rules! structure_text_validator {
726 ($(#[$meta:meta])* $name:ident, $options:ident, $validate:ident) => {
727 $(#[$meta])*
728 pub fn $name(
729 spec: &JsonText,
730 files: Option<&JsonText>,
731 basepath: Option<&JsonText>,
732 ) -> Result<(), ValidateError> {
733 structure_text(spec, files, basepath, |spec, files, basepath| {
734 let options = $options { files, loader: None, basepath };
735 Ok(crate::$validate(spec, &options)?)
736 })
737 }
738 };
739}
740
741structure_text_validator! {
742 validate_list_text, ValidateListOptions, validate_list
745}
746
747structure_text_validator! {
748 validate_detail_text, ValidateDetailOptions, validate_detail
751}
752
753#[cfg(test)]
754mod tests {
755 use super::*;
756
757 fn object(count: usize) -> String {
759 let members: Vec<String> = (0..count).map(|i| format!("\"m{i}\":{i}")).collect();
760 format!("{{{}}}", members.join(","))
761 }
762
763 #[test]
764 fn repeated_member_is_rejected() {
765 for source in [
766 r#"{"a":1,"b":2,"a":3}"#,
767 r#"{"a":{"b":1,"\u0062":2}}"#,
768 r#"[{"a":1,"a":2}]"#,
769 ] {
770 assert!(JsonText::parse(source).is_err(), "{source}");
771 }
772 }
773
774 #[test]
775 fn decoded_object_values_keep_their_names_and_order() {
776 let source = r#"{"a":1,"b":{"x":2,"y":3},"c":[4,{"z":5}]}"#;
777 let parsed = JsonText::parse(source).unwrap();
778 let decoded = super::json_text_to_value(&parsed).unwrap();
779 assert_eq!(serde_json::to_string(&decoded).unwrap(), source);
780 }
781
782 #[test]
786 fn reading_many_members_ends() {
787 JsonText::parse(&object(100_000)).unwrap();
788 assert!(JsonText::parse(&format!("{{\"m0\":0,{}", &object(100_000)[1..])).is_err());
789 }
790}