1use itertools::Itertools;
58use pdk_core::log::debug;
59use thiserror::Error;
60
61mod parser;
62mod validator;
63
64use parser::{DataStream, JsonParser, ParserContext};
65use validator::JsonConstraintValidator;
66
67#[derive(Debug, Clone)]
69struct JsonConstraints {
70 max_container_depth: Option<usize>,
71 max_object_entry_count: Option<usize>,
72 max_object_entry_name_length: Option<usize>,
73 max_array_element_count: Option<usize>,
74 max_string_value_length: Option<usize>,
75}
76
77#[derive(Debug, PartialEq, Eq)]
79pub enum ValidationResult {
80 Incomplete,
82
83 Complete,
85}
86
87#[non_exhaustive]
89#[derive(Debug, Error, PartialEq, Eq)]
90pub enum ValidationError {
91 #[error("Max depth exceeded.")]
93 MaxDepthExceeded,
94
95 #[error("Array exceeded max length")]
97 ArrayMaxLengthExceeded,
98
99 #[error("Object exceeded max length")]
101 ObjectMaxLengthExceeded,
102
103 #[error("String exceeded max length")]
105 StringMaxLengthExceeded,
106
107 #[error("Unable to process payload.")]
109 UnableToProcessPayload,
110
111 #[error("Unable to process payload. Could not complete token {0}")]
113 InvalidToken(String),
114
115 #[error("Error at idx {0}. Did not expect any more characters, found {1}")]
117 UnexpectedCharacter(usize, char),
118
119 #[error("Invalid json")]
121 InvalidJson,
122
123 #[error("Non-UTF8 payload")]
125 NonUtf8,
126}
127
128pub struct JsonValidatorBuilder {
133 constraints: JsonConstraints,
134}
135
136impl JsonValidatorBuilder {
137 pub fn new() -> Self {
139 Self {
140 constraints: JsonConstraints {
141 max_container_depth: None,
142 max_object_entry_count: None,
143 max_object_entry_name_length: None,
144 max_array_element_count: None,
145 max_string_value_length: None,
146 },
147 }
148 }
149
150 pub fn with_max_depth(mut self, depth: usize) -> Self {
152 self.constraints.max_container_depth = Some(depth);
153 self
154 }
155
156 pub fn with_max_array_length(mut self, length: usize) -> Self {
158 self.constraints.max_array_element_count = Some(length);
159 self
160 }
161
162 pub fn with_max_string_length(mut self, length: usize) -> Self {
164 self.constraints.max_string_value_length = Some(length);
165 self
166 }
167
168 pub fn with_max_object_entries(mut self, count: usize) -> Self {
170 self.constraints.max_object_entry_count = Some(count);
171 self
172 }
173
174 pub fn with_max_key_length(mut self, length: usize) -> Self {
176 self.constraints.max_object_entry_name_length = Some(length);
177 self
178 }
179
180 pub fn build(self) -> JsonValidator {
182 JsonValidator::new(self.constraints)
183 }
184}
185
186impl Default for JsonValidatorBuilder {
187 fn default() -> Self {
188 Self::new()
189 }
190}
191
192pub struct JsonValidator {
199 constraint_validator: JsonConstraintValidator,
200 parser: ParserContext,
201 data_stream_idx: (usize, usize), buffer: Vec<u8>,
203}
204
205impl JsonValidator {
206 fn new(constraints: JsonConstraints) -> Self {
207 let constraint_validator = JsonConstraintValidator::new(
208 constraints.max_array_element_count,
209 constraints.max_string_value_length,
210 constraints.max_object_entry_count,
211 constraints.max_object_entry_name_length,
212 constraints.max_container_depth,
213 );
214
215 Self {
216 constraint_validator,
217 parser: ParserContext::new(),
218 data_stream_idx: (0, 0),
219 buffer: Vec::new(),
220 }
221 }
222
223 fn validate_json(&mut self, json: &str, end_of_stream: bool) -> Result<(), ValidationError> {
224 let chars = json.as_bytes();
225 let (idx, offset) = self.data_stream_idx;
226 let mut stream = DataStream::new(chars, idx + offset);
227
228 while !stream.is_empty() {
229 if self.parser.is_empty() {
230 if stream.consume().map(parser::is_whitespace).unwrap_or(false) {
231 continue;
232 }
233 return Err(ValidationError::UnexpectedCharacter(
234 stream.idx(),
235 stream.peek().unwrap_or_default(),
236 ));
237 }
238 let current_parser = self.parser.current().unwrap();
239 let result =
240 current_parser.parse(&mut stream, &mut self.parser, &self.constraint_validator);
241 if result.is_err() {
242 debug!(
243 "Error at : {}{}",
244 stream.peek_range(-20).unwrap_or(""),
245 stream.peek_range(20).unwrap_or("")
246 )
247 }
248 result?;
249 }
250
251 let stream_idx = stream.idx();
252 self.data_stream_idx = (stream_idx, 0);
253
254 let processing_valid_number = !self.parser.is_empty() && self.is_processing_valid_number();
255 let is_valid = self.parser.is_empty() || processing_valid_number;
256 let is_empty = self.data_stream_idx.0 + self.data_stream_idx.1 == 0;
257
258 if !is_empty && is_valid {
259 Ok(())
260 } else if !end_of_stream {
261 Ok(())
263 } else {
264 return Err(ValidationError::InvalidJson);
265 }
266 }
267
268 fn is_processing_valid_number(&self) -> bool {
269 if let Some(JsonParser::Number { expected }) = self.parser.current() {
270 return matches!(expected, parser::NumberStateMachine::DigitPeriodOrExponent);
271 }
272 false
273 }
274
275 fn split_utf8(bytes: &[u8]) -> (&[u8], &[u8]) {
280 if bytes.is_empty() {
281 return (&[], &[]);
282 }
283 if Self::is_ascii(*bytes.last().unwrap()) {
284 return (bytes, &[]);
285 }
286
287 if let Some((reversed_idx, b)) = bytes
288 .iter()
289 .rev()
290 .find_position(|&&b| Self::is_utf8_lead(b))
291 {
292 let utf8_char_expected_len = Self::utf8_char_size(*b);
293 let utf8_char_actual_len = reversed_idx + 1;
294 let idx = if utf8_char_expected_len as usize == utf8_char_actual_len {
295 bytes.len()
296 } else {
297 bytes
298 .len()
299 .checked_sub(reversed_idx + 1)
300 .unwrap_or_default()
301 };
302 (&bytes[..idx], &bytes[idx..])
303 } else {
304 (&[], &[])
305 }
306 }
307
308 #[inline]
309 fn is_ascii(b: u8) -> bool {
310 !Self::is_not_ascii(b)
311 }
312
313 #[inline]
314 fn is_utf8_lead(b: u8) -> bool {
315 (b & 0b11000000_u8) == 0b11000000_u8
316 }
317
318 #[inline]
319 fn is_not_ascii(byte: u8) -> bool {
320 byte & 0b10000000_u8 == 0b10000000_u8
321 }
322
323 fn utf8_char_size(mut lead_byte: u8) -> u8 {
324 let mut counter = 0;
325 while Self::is_not_ascii(lead_byte) {
326 counter += 1;
327 lead_byte <<= 1;
328 }
329 counter
330 }
331
332 pub fn validate_chunk(
350 &mut self,
351 chunk: &[u8],
352 end_of_stream: bool,
353 ) -> Result<ValidationResult, ValidationError> {
354 self.buffer.extend_from_slice(chunk);
356
357 let (valid_utf8_ref, remainder_ref) = Self::split_utf8(&self.buffer);
359
360 if valid_utf8_ref.is_empty() && !end_of_stream {
362 return Ok(ValidationResult::Incomplete);
363 }
364
365 if valid_utf8_ref.is_empty() && end_of_stream {
367 return Err(ValidationError::NonUtf8);
368 }
369
370 let valid_utf8_vec = valid_utf8_ref.to_vec();
373 let remainder_vec = if !end_of_stream {
374 remainder_ref.to_vec()
375 } else {
376 Vec::new()
377 };
378
379 let json_str =
381 std::str::from_utf8(&valid_utf8_vec).map_err(|_| ValidationError::NonUtf8)?;
382
383 match self.validate_json(json_str, end_of_stream) {
385 Ok(()) if end_of_stream => {
386 self.buffer.clear();
388 Ok(ValidationResult::Complete)
389 }
390 Ok(()) => {
391 self.buffer = remainder_vec;
393 Ok(ValidationResult::Incomplete)
394 }
395 Err(e) => {
396 self.buffer.clear();
398 Err(e)
399 }
400 }
401 }
402
403 pub fn reset(&mut self) {
405 self.parser = ParserContext::new();
406 self.buffer.clear();
407 self.data_stream_idx = (0, 0);
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn builder_creates_validator_without_constraints() {
417 let mut validator = JsonValidatorBuilder::new().build();
418 let result = validator.validate_chunk(b"{\"key\": \"value\"}", true);
419 assert!(result.is_ok());
420 assert_eq!(result.unwrap(), ValidationResult::Complete);
421 }
422
423 #[test]
424 fn builder_configures_max_depth() {
425 let mut validator = JsonValidatorBuilder::new().with_max_depth(2).build();
426
427 let deep_json = r#"{"a": {"b": {"c": "value"}}}"#;
429 let result = validator.validate_chunk(deep_json.as_bytes(), true);
430 assert!(result.is_err());
431 assert!(matches!(
432 result.unwrap_err(),
433 ValidationError::MaxDepthExceeded
434 ));
435 }
436
437 #[test]
438 fn validator_handles_incremental_chunks() {
439 let mut validator = JsonValidatorBuilder::new().build();
440
441 let result1 = validator.validate_chunk(b"{\"key\": \"val", false);
443 assert!(result1.is_ok());
444 assert_eq!(result1.unwrap(), ValidationResult::Incomplete);
445
446 let result2 = validator.validate_chunk(b"ue\"}", true);
448 assert!(result2.is_ok());
449 assert_eq!(result2.unwrap(), ValidationResult::Complete);
450 }
451
452 #[test]
453 fn validator_rejects_invalid_json() {
454 let mut validator = JsonValidatorBuilder::new().build();
455 let result = validator.validate_chunk(b"{invalid", true);
456 assert!(result.is_err());
457 assert!(matches!(
458 result.unwrap_err(),
459 ValidationError::InvalidJson
460 | ValidationError::UnableToProcessPayload
461 | ValidationError::InvalidToken(_)
462 ));
463 }
464
465 #[test]
466 fn validator_reset_clears_state() {
467 let mut validator = JsonValidatorBuilder::new().build();
468
469 let _ = validator.validate_chunk(b"{\"key\": \"value\"}", true);
471
472 validator.reset();
474
475 let result = validator.validate_chunk(b"{\"another\": \"json\"}", true);
477 assert!(result.is_ok());
478 assert_eq!(result.unwrap(), ValidationResult::Complete);
479 }
480
481 #[test]
482 fn validator_rejects_non_utf8_payload() {
483 let mut validator = JsonValidatorBuilder::new().build();
484 let invalid_utf8: &[u8] = &[0xFF, 0xFE, 0xFD];
486 let result = validator.validate_chunk(invalid_utf8, true);
487 assert!(result.is_err());
488 assert!(matches!(result.unwrap_err(), ValidationError::NonUtf8));
489 }
490
491 #[test]
492 fn split_utf8_bytes_are_not_changed() {
493 let original = "my very ∆˚∆ utf8 string".as_bytes();
494 let (prefix, remainder) = JsonValidator::split_utf8(original);
495 assert_eq!(original, prefix);
496 assert_eq!(&[] as &[u8], remainder);
497 }
498
499 #[test]
500 fn split_utf8_string_with_incomplete_char_splits_correctly() {
501 let original: &[u8] = &[
502 0b01101101, 0b01111001, 0b00100000, 0b01110110, 0b01100101, 0b01110010, 0b01111001,
503 0b00100000, 0b11110010, 0b10100010, 0b10100010,
504 ];
505 let expected_prefix: &[u8] = &[
506 0b01101101, 0b01111001, 0b00100000, 0b01110110, 0b01100101, 0b01110010, 0b01111001,
507 0b00100000,
508 ];
509 let expected_remainder: &[u8] = &[0b11110010, 0b10100010, 0b10100010];
510
511 let (prefix, remainder) = JsonValidator::split_utf8(original);
512 assert_eq!(expected_prefix, prefix);
513 assert_eq!(expected_remainder, remainder);
514 }
515
516 #[test]
517 fn split_utf8_empty_bytes_returns_empty() {
518 let (prefix, remainder) = JsonValidator::split_utf8(&[]);
519 assert_eq!(&[] as &[u8], prefix);
520 assert_eq!(&[] as &[u8], remainder);
521 }
522
523 #[test]
524 fn split_utf8_ascii_string_returns_full_string() {
525 let ascii = b"hello world";
526 let (prefix, remainder) = JsonValidator::split_utf8(ascii);
527 assert_eq!(ascii, prefix);
528 assert_eq!(&[] as &[u8], remainder);
529 }
530
531 #[test]
532 fn plain_positive_integer_is_not_blocked() {
533 let mut validator = JsonValidatorBuilder::new().build();
535 let result = validator.validate_chunk(b"1234", true);
536 assert_eq!(result, Ok(ValidationResult::Complete));
537 }
538
539 #[test]
540 fn plain_negative_integer_is_not_blocked() {
541 let mut validator = JsonValidatorBuilder::new().build();
542 let result = validator.validate_chunk(b"-42", true);
543 assert_eq!(result, Ok(ValidationResult::Complete));
544 }
545
546 #[test]
547 fn exponential_integer_notation_is_not_blocked() {
548 let mut validator = JsonValidatorBuilder::new().build();
549 let result = validator.validate_chunk(b"1e10", true);
550 assert_eq!(result, Ok(ValidationResult::Complete));
551 }
552
553 #[test]
554 fn exponential_float_notation_uppercase_is_not_blocked() {
555 let mut validator = JsonValidatorBuilder::new().build();
556 let result = validator.validate_chunk(b"8.8E9", true);
557 assert_eq!(result, Ok(ValidationResult::Complete));
558 }
559
560 #[test]
561 fn exponential_with_negative_exponent_is_not_blocked() {
562 let mut validator = JsonValidatorBuilder::new().build();
563 let result = validator.validate_chunk(b"2.5e-3", true);
564 assert_eq!(result, Ok(ValidationResult::Complete));
565 }
566
567 #[test]
568 fn exponential_with_positive_exponent_is_not_blocked() {
569 let mut validator = JsonValidatorBuilder::new().build();
570 let result = validator.validate_chunk(b"-14.15e+13", true);
571 assert_eq!(result, Ok(ValidationResult::Complete));
572 }
573
574 #[test]
575 fn exponential_notation_in_object_is_not_blocked() {
576 let mut validator = JsonValidatorBuilder::new().build();
577 let result = validator.validate_chunk(br#"{"value": 1e10}"#, true);
578 assert_eq!(result, Ok(ValidationResult::Complete));
579 }
580
581 #[test]
582 fn exponential_notation_in_array_is_not_blocked() {
583 let mut validator = JsonValidatorBuilder::new().build();
584 let result = validator.validate_chunk(b"[1e10, 8.8E9, 2.5e-3]", true);
585 assert_eq!(result, Ok(ValidationResult::Complete));
586 }
587
588 #[test]
589 fn incomplete_number_missing_exponent_digits_in_array_is_blocked() {
590 let mut validator = JsonValidatorBuilder::new().build();
591 let result = validator.validate_chunk(b"[1e, 2]", true);
592 assert_eq!(result, Err(ValidationError::UnableToProcessPayload));
593 }
594
595 #[test]
596 fn incomplete_number_missing_fraction_digits_in_array_is_blocked() {
597 let mut validator = JsonValidatorBuilder::new().build();
598 let result = validator.validate_chunk(b"[1., 2]", true);
599 assert_eq!(result, Err(ValidationError::UnableToProcessPayload));
600 }
601
602 #[test]
603 fn incomplete_number_bare_minus_in_array_is_blocked() {
604 let mut validator = JsonValidatorBuilder::new().build();
605 let result = validator.validate_chunk(b"[-]", true);
606 assert_eq!(result, Err(ValidationError::UnableToProcessPayload));
607 }
608
609 #[test]
610 fn incomplete_number_exponent_with_sign_only_in_array_is_blocked() {
611 let mut validator = JsonValidatorBuilder::new().build();
612 let result = validator.validate_chunk(b"[1e+]", true);
613 assert_eq!(result, Err(ValidationError::UnableToProcessPayload));
614 }
615
616 #[test]
617 fn incomplete_standalone_number_missing_exponent_digits_is_blocked() {
618 let mut validator = JsonValidatorBuilder::new().build();
619 let result = validator.validate_chunk(b"1e", true);
620 assert_eq!(result, Err(ValidationError::InvalidJson));
621 }
622
623 #[test]
624 fn incomplete_standalone_number_missing_fraction_digits_is_blocked() {
625 let mut validator = JsonValidatorBuilder::new().build();
626 let result = validator.validate_chunk(b"1.", true);
627 assert_eq!(result, Err(ValidationError::InvalidJson));
628 }
629
630 #[test]
631 fn incomplete_standalone_number_bare_minus_is_blocked() {
632 let mut validator = JsonValidatorBuilder::new().build();
633 let result = validator.validate_chunk(b"-", true);
634 assert_eq!(result, Err(ValidationError::InvalidJson));
635 }
636
637 #[test]
638 fn incomplete_standalone_number_exponent_with_sign_only_is_blocked() {
639 let mut validator = JsonValidatorBuilder::new().build();
640 let result = validator.validate_chunk(b"1e+", true);
641 assert_eq!(result, Err(ValidationError::InvalidJson));
642 }
643
644 #[test]
645 fn valid_string_json_is_not_blocked() {
646 let mut validator = JsonValidatorBuilder::new().build();
647 let result = validator.validate_chunk(b"\"string is valid json\"", true);
648 assert_eq!(result, Ok(ValidationResult::Complete));
649 }
650
651 #[test]
652 fn two_objects_payload_is_blocked() {
653 let mut validator = JsonValidatorBuilder::new().build();
654 let result = validator.validate_chunk(br#"{"menu": "file"}{"foo":"bar"}"#, true);
655 assert!(matches!(
656 result,
657 Err(ValidationError::UnexpectedCharacter(..))
658 ));
659 }
660
661 #[test]
662 fn two_numbers_payload_is_blocked() {
663 let mut validator = JsonValidatorBuilder::new().build();
664 let result = validator.validate_chunk(b"123 456", true);
665 assert!(matches!(
666 result,
667 Err(ValidationError::UnexpectedCharacter(..))
668 ));
669 }
670
671 #[test]
672 fn string_and_number_payload_is_blocked() {
673 let mut validator = JsonValidatorBuilder::new().build();
674 let result = validator.validate_chunk(b"\"string1\" 123", true);
675 assert!(matches!(
676 result,
677 Err(ValidationError::UnexpectedCharacter(..))
678 ));
679 }
680
681 #[test]
682 fn array_payload_is_not_blocked() {
683 let mut validator = JsonValidatorBuilder::new().build();
684 let result = validator.validate_chunk(b"[1, 2, \"a\", \"b\"]", true);
685 assert_eq!(result, Ok(ValidationResult::Complete));
686 }
687
688 #[test]
689 fn nested_object_in_array_is_not_blocked() {
690 let mut validator = JsonValidatorBuilder::new().build();
691 let result = validator.validate_chunk(br#"[1, 2, "a", "b", {"menu": "file"}]"#, true);
692 assert_eq!(result, Ok(ValidationResult::Complete));
693 }
694
695 #[test]
696 fn payload_with_whitespace_is_not_blocked() {
697 let mut validator = JsonValidatorBuilder::new().build();
698 let result = validator.validate_chunk("{\"menu\": \r\n \"file\" \t}".as_bytes(), true);
699 assert_eq!(result, Ok(ValidationResult::Complete));
700 }
701}