Skip to main content

pdk_json_validator_lib/
lib.rs

1// Copyright (c) 2026, Salesforce, Inc.,
2// All rights reserved.
3// For full license text, see the LICENSE.txt file
4
5//! PDK JSON Validator Library
6//!
7//! Provides JSON validation functionality with configurable constraints,
8//! supporting incremental/streaming validation of JSON payloads.
9//!
10//! - Incremental JSON validation (chunk-based streaming)
11//! - Configurable constraints (depth, array length, object entries, string/key lengths)
12//! - UTF-8 boundary handling for incomplete chunks
13//!
14//! ## Primary types
15//!
16//! - [`JsonValidator`]: validator for JSON chunks incrementally
17//! - [`ValidationResult`]: result indicating validation status
18//! - [`ValidationError`]: error type for validation failures
19//! - [`JsonValidatorBuilder`]: builder for creating validator instances
20//!
21//! ## Example
22//!
23//! ```rust,no_run
24//! use pdk::json_validator::{JsonValidatorBuilder, ValidationResult, ValidationError};
25//!
26//! let mut validator = JsonValidatorBuilder::new()
27//!     .with_max_depth(10)
28//!     .with_max_array_length(1000)
29//!     .with_max_string_length(10000)
30//!     .build();
31//!
32//! // Validate first chunk
33//! match validator.validate_chunk(b"{\"key\": \"val", false) {
34//!     Ok(ValidationResult::Incomplete) => {
35//!         // Wait for more chunks
36//!     }
37//!     Err(e) => {
38//!         eprintln!("Validation error: {}", e);
39//!     }
40//!     _ => {}
41//! }
42//!
43//! // Validate final chunk
44//! match validator.validate_chunk(b"ue\"}", true) {
45//!     Ok(ValidationResult::Complete) => {
46//!         // JSON valid
47//!     }
48//!     Err(ValidationError::MaxDepthExceeded) => {
49//!         // Handle specific constraint violation
50//!     }
51//!     Err(e) => {
52//!         eprintln!("Validation error: {}", e);
53//!     }
54//! }
55//! ```
56
57use 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// JsonConstraints is private - only used internally
68#[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/// Result of validating a JSON chunk
78#[derive(Debug, PartialEq, Eq)]
79pub enum ValidationResult {
80    /// JSON is valid but incomplete (waiting for more chunks)
81    Incomplete,
82
83    /// JSON is complete and valid
84    Complete,
85}
86
87/// Errors that can occur during JSON validation
88#[non_exhaustive]
89#[derive(Debug, Error, PartialEq, Eq)]
90pub enum ValidationError {
91    /// Maximum depth constraint violated
92    #[error("Max depth exceeded.")]
93    MaxDepthExceeded,
94
95    /// Array length constraint violated
96    #[error("Array exceeded max length")]
97    ArrayMaxLengthExceeded,
98
99    /// Object entry count constraint violated
100    #[error("Object exceeded max length")]
101    ObjectMaxLengthExceeded,
102
103    /// String length constraint violated
104    #[error("String exceeded max length")]
105    StringMaxLengthExceeded,
106
107    /// Unable to process payload due to invalid syntax
108    #[error("Unable to process payload.")]
109    UnableToProcessPayload,
110
111    /// Invalid token encountered
112    #[error("Unable to process payload. Could not complete token {0}")]
113    InvalidToken(String),
114
115    /// Unexpected character at specific index
116    #[error("Error at idx {0}. Did not expect any more characters, found {1}")]
117    UnexpectedCharacter(usize, char),
118
119    /// Invalid JSON structure
120    #[error("Invalid json")]
121    InvalidJson,
122
123    /// Payload is not valid UTF-8
124    #[error("Non-UTF8 payload")]
125    NonUtf8,
126}
127
128/// Builder for creating JSON validation instances
129///
130/// The builder receives configuration values directly and constructs
131/// constraints internally.
132pub struct JsonValidatorBuilder {
133    constraints: JsonConstraints,
134}
135
136impl JsonValidatorBuilder {
137    /// Creates a new builder without restrictions (only validates JSON syntax)
138    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    /// Sets the maximum depth of nested containers
151    pub fn with_max_depth(mut self, depth: usize) -> Self {
152        self.constraints.max_container_depth = Some(depth);
153        self
154    }
155
156    /// Sets the maximum number of elements in arrays
157    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    /// Sets the maximum length of string values
163    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    /// Sets the maximum number of entries in objects
169    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    /// Sets the maximum length of key names in objects
175    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    /// Builds the validation instance with the specified configuration
181    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
192/// JSON validator with configurable constraints.
193///
194/// Handles incremental/streaming validation of JSON payloads and maintains internal
195/// state between chunk validations.
196///
197/// Instances should be created using [`JsonValidatorBuilder`].
198pub struct JsonValidator {
199    constraint_validator: JsonConstraintValidator,
200    parser: ParserContext,
201    data_stream_idx: (usize, usize), // (idx, offset)
202    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            // JSON incomplete but valid so far
262            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    /// Returns the longest UTF-8 valid prefix and the remainder
276    ///
277    /// This method handles UTF-8 boundaries correctly, ensuring that
278    /// incomplete UTF-8 characters are not split across chunks.
279    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    /// Validates a JSON chunk incrementally
333    ///
334    /// This method can be called multiple times with different chunks
335    /// of the same JSON. The validator maintains internal state to handle
336    /// incremental validation and UTF-8 boundaries.
337    ///
338    /// # Arguments
339    ///
340    /// * `chunk` - Bytes chunk to validate. Can be a fragment of the complete JSON.
341    /// * `end_of_stream` - `true` if this is the last chunk of the stream, `false` if there are more chunks pending.
342    ///
343    /// # Returns
344    ///
345    /// * `Ok(ValidationResult::Incomplete)` - The chunk is valid but the JSON is incomplete.
346    ///   `validate_chunk()` should be called again with the next chunk.
347    /// * `Ok(ValidationResult::Complete)` - The JSON is complete and valid (only when `end_of_stream = true`).
348    /// * `Err(ValidationError)` - Validation error (invalid JSON or constraint violation).
349    pub fn validate_chunk(
350        &mut self,
351        chunk: &[u8],
352        end_of_stream: bool,
353    ) -> Result<ValidationResult, ValidationError> {
354        // Accumulate chunk in buffer
355        self.buffer.extend_from_slice(chunk);
356
357        // Handle UTF-8 boundaries (split into valid prefix + remainder)
358        let (valid_utf8_ref, remainder_ref) = Self::split_utf8(&self.buffer);
359
360        // If no valid UTF-8 and not end_of_stream, wait for more data
361        if valid_utf8_ref.is_empty() && !end_of_stream {
362            return Ok(ValidationResult::Incomplete);
363        }
364
365        // If no valid UTF-8 and end_of_stream, error
366        if valid_utf8_ref.is_empty() && end_of_stream {
367            return Err(ValidationError::NonUtf8);
368        }
369
370        // Copy slices before mutating self (needed for borrow checker)
371        // Only copy remainder if it can be needed (not end_of_stream)
372        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        // Convert to UTF-8 string
380        let json_str =
381            std::str::from_utf8(&valid_utf8_vec).map_err(|_| ValidationError::NonUtf8)?;
382
383        // Validate JSON with incremental parser
384        match self.validate_json(json_str, end_of_stream) {
385            Ok(()) if end_of_stream => {
386                // Clear buffer
387                self.buffer.clear();
388                Ok(ValidationResult::Complete)
389            }
390            Ok(()) => {
391                // Save remainder for next chunk
392                self.buffer = remainder_vec;
393                Ok(ValidationResult::Incomplete)
394            }
395            Err(e) => {
396                // Clear buffer on error
397                self.buffer.clear();
398                Err(e)
399            }
400        }
401    }
402
403    /// Resets the validator state to start a new validation
404    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        // JSON with depth 3 should fail
428        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        // First incomplete chunk
442        let result1 = validator.validate_chunk(b"{\"key\": \"val", false);
443        assert!(result1.is_ok());
444        assert_eq!(result1.unwrap(), ValidationResult::Incomplete);
445
446        // Second complete chunk
447        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        // Validate a JSON
470        let _ = validator.validate_chunk(b"{\"key\": \"value\"}", true);
471
472        // Reset
473        validator.reset();
474
475        // Should be able to validate another JSON from scratch
476        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        // Invalid UTF-8 bytes
485        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        // pre-migration InvalidJsonTest: numericPayloadIsNotBlocked
534        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}