@@ -147,6 +147,228 @@ impl Validate for SingleItemRequiredValidator {
147147 }
148148}
149149
150+ /// Specialized validator for exactly 2 required properties.
151+ /// Uses fixed-size array and unrolled checks to avoid Vec/iterator overhead.
152+ pub ( crate ) struct Required2Validator {
153+ first : String ,
154+ second : String ,
155+ location : Location ,
156+ }
157+
158+ impl Required2Validator {
159+ #[ inline]
160+ pub ( crate ) fn compile (
161+ first : String ,
162+ second : String ,
163+ location : Location ,
164+ ) -> CompilationResult < ' static > {
165+ Ok ( Box :: new ( Required2Validator {
166+ first,
167+ second,
168+ location,
169+ } ) )
170+ }
171+ }
172+
173+ impl Validate for Required2Validator {
174+ #[ inline]
175+ fn is_valid ( & self , instance : & Value , _ctx : & mut ValidationContext ) -> bool {
176+ if let Value :: Object ( item) = instance {
177+ item. len ( ) >= 2 && item. contains_key ( & self . first ) && item. contains_key ( & self . second )
178+ } else {
179+ true
180+ }
181+ }
182+
183+ fn validate < ' i > (
184+ & self ,
185+ instance : & ' i Value ,
186+ location : & LazyLocation ,
187+ tracker : Option < & RefTracker > ,
188+ _ctx : & mut ValidationContext ,
189+ ) -> Result < ( ) , ValidationError < ' i > > {
190+ if let Value :: Object ( item) = instance {
191+ if !item. contains_key ( & self . first ) {
192+ return Err ( ValidationError :: required (
193+ self . location . clone ( ) ,
194+ crate :: paths:: capture_evaluation_path ( tracker, & self . location ) ,
195+ location. into ( ) ,
196+ instance,
197+ Value :: String ( self . first . clone ( ) ) ,
198+ ) ) ;
199+ }
200+ if !item. contains_key ( & self . second ) {
201+ return Err ( ValidationError :: required (
202+ self . location . clone ( ) ,
203+ crate :: paths:: capture_evaluation_path ( tracker, & self . location ) ,
204+ location. into ( ) ,
205+ instance,
206+ Value :: String ( self . second . clone ( ) ) ,
207+ ) ) ;
208+ }
209+ }
210+ Ok ( ( ) )
211+ }
212+
213+ fn iter_errors < ' i > (
214+ & self ,
215+ instance : & ' i Value ,
216+ location : & LazyLocation ,
217+ tracker : Option < & RefTracker > ,
218+ _ctx : & mut ValidationContext ,
219+ ) -> ErrorIterator < ' i > {
220+ if let Value :: Object ( item) = instance {
221+ let eval_path = crate :: paths:: capture_evaluation_path ( tracker, & self . location ) ;
222+ let mut errors = Vec :: new ( ) ;
223+ if !item. contains_key ( & self . first ) {
224+ errors. push ( ValidationError :: required (
225+ self . location . clone ( ) ,
226+ eval_path. clone ( ) ,
227+ location. into ( ) ,
228+ instance,
229+ Value :: String ( self . first . clone ( ) ) ,
230+ ) ) ;
231+ }
232+ if !item. contains_key ( & self . second ) {
233+ errors. push ( ValidationError :: required (
234+ self . location . clone ( ) ,
235+ eval_path,
236+ location. into ( ) ,
237+ instance,
238+ Value :: String ( self . second . clone ( ) ) ,
239+ ) ) ;
240+ }
241+ if !errors. is_empty ( ) {
242+ return ErrorIterator :: from_iterator ( errors. into_iter ( ) ) ;
243+ }
244+ }
245+ no_error ( )
246+ }
247+ }
248+
249+ /// Specialized validator for exactly 3 required properties.
250+ /// Uses fixed-size fields and unrolled checks to avoid Vec/iterator overhead.
251+ pub ( crate ) struct Required3Validator {
252+ first : String ,
253+ second : String ,
254+ third : String ,
255+ location : Location ,
256+ }
257+
258+ impl Required3Validator {
259+ #[ inline]
260+ pub ( crate ) fn compile (
261+ first : String ,
262+ second : String ,
263+ third : String ,
264+ location : Location ,
265+ ) -> CompilationResult < ' static > {
266+ Ok ( Box :: new ( Required3Validator {
267+ first,
268+ second,
269+ third,
270+ location,
271+ } ) )
272+ }
273+ }
274+
275+ impl Validate for Required3Validator {
276+ #[ inline]
277+ fn is_valid ( & self , instance : & Value , _ctx : & mut ValidationContext ) -> bool {
278+ if let Value :: Object ( item) = instance {
279+ item. len ( ) >= 3
280+ && item. contains_key ( & self . first )
281+ && item. contains_key ( & self . second )
282+ && item. contains_key ( & self . third )
283+ } else {
284+ true
285+ }
286+ }
287+
288+ fn validate < ' i > (
289+ & self ,
290+ instance : & ' i Value ,
291+ location : & LazyLocation ,
292+ tracker : Option < & RefTracker > ,
293+ _ctx : & mut ValidationContext ,
294+ ) -> Result < ( ) , ValidationError < ' i > > {
295+ if let Value :: Object ( item) = instance {
296+ if !item. contains_key ( & self . first ) {
297+ return Err ( ValidationError :: required (
298+ self . location . clone ( ) ,
299+ crate :: paths:: capture_evaluation_path ( tracker, & self . location ) ,
300+ location. into ( ) ,
301+ instance,
302+ Value :: String ( self . first . clone ( ) ) ,
303+ ) ) ;
304+ }
305+ if !item. contains_key ( & self . second ) {
306+ return Err ( ValidationError :: required (
307+ self . location . clone ( ) ,
308+ crate :: paths:: capture_evaluation_path ( tracker, & self . location ) ,
309+ location. into ( ) ,
310+ instance,
311+ Value :: String ( self . second . clone ( ) ) ,
312+ ) ) ;
313+ }
314+ if !item. contains_key ( & self . third ) {
315+ return Err ( ValidationError :: required (
316+ self . location . clone ( ) ,
317+ crate :: paths:: capture_evaluation_path ( tracker, & self . location ) ,
318+ location. into ( ) ,
319+ instance,
320+ Value :: String ( self . third . clone ( ) ) ,
321+ ) ) ;
322+ }
323+ }
324+ Ok ( ( ) )
325+ }
326+
327+ fn iter_errors < ' i > (
328+ & self ,
329+ instance : & ' i Value ,
330+ location : & LazyLocation ,
331+ tracker : Option < & RefTracker > ,
332+ _ctx : & mut ValidationContext ,
333+ ) -> ErrorIterator < ' i > {
334+ if let Value :: Object ( item) = instance {
335+ let eval_path = crate :: paths:: capture_evaluation_path ( tracker, & self . location ) ;
336+ let mut errors = Vec :: new ( ) ;
337+ if !item. contains_key ( & self . first ) {
338+ errors. push ( ValidationError :: required (
339+ self . location . clone ( ) ,
340+ eval_path. clone ( ) ,
341+ location. into ( ) ,
342+ instance,
343+ Value :: String ( self . first . clone ( ) ) ,
344+ ) ) ;
345+ }
346+ if !item. contains_key ( & self . second ) {
347+ errors. push ( ValidationError :: required (
348+ self . location . clone ( ) ,
349+ eval_path. clone ( ) ,
350+ location. into ( ) ,
351+ instance,
352+ Value :: String ( self . second . clone ( ) ) ,
353+ ) ) ;
354+ }
355+ if !item. contains_key ( & self . third ) {
356+ errors. push ( ValidationError :: required (
357+ self . location . clone ( ) ,
358+ eval_path,
359+ location. into ( ) ,
360+ instance,
361+ Value :: String ( self . third . clone ( ) ) ,
362+ ) ) ;
363+ }
364+ if !errors. is_empty ( ) {
365+ return ErrorIterator :: from_iterator ( errors. into_iter ( ) ) ;
366+ }
367+ }
368+ no_error ( )
369+ }
370+ }
371+
150372#[ inline]
151373pub ( crate ) fn compile < ' a > (
152374 ctx : & compiler:: Context ,
@@ -164,8 +386,8 @@ pub(crate) fn compile_with_path(
164386) -> Option < CompilationResult < ' _ > > {
165387 // IMPORTANT: If this function will ever return `None`, adjust `dependencies.rs` accordingly
166388 match schema {
167- Value :: Array ( items) => {
168- if items . len ( ) == 1 {
389+ Value :: Array ( items) => match items . len ( ) {
390+ 1 => {
169391 let item = & items[ 0 ] ;
170392 if let Value :: String ( item) = item {
171393 Some ( SingleItemRequiredValidator :: compile ( item, location) )
@@ -178,10 +400,48 @@ pub(crate) fn compile_with_path(
178400 JsonType :: String ,
179401 ) ) )
180402 }
181- } else {
182- Some ( RequiredValidator :: compile ( items, location) )
183403 }
184- }
404+ 2 => {
405+ let ( first, second) = ( & items[ 0 ] , & items[ 1 ] ) ;
406+ match ( first, second) {
407+ ( Value :: String ( first) , Value :: String ( second) ) => Some (
408+ Required2Validator :: compile ( first. clone ( ) , second. clone ( ) , location) ,
409+ ) ,
410+ ( Value :: String ( _) , other) | ( other, _) => {
411+ Some ( Err ( ValidationError :: single_type_error (
412+ location. clone ( ) ,
413+ location,
414+ Location :: new ( ) ,
415+ other,
416+ JsonType :: String ,
417+ ) ) )
418+ }
419+ }
420+ }
421+ 3 => {
422+ let ( first, second, third) = ( & items[ 0 ] , & items[ 1 ] , & items[ 2 ] ) ;
423+ match ( first, second, third) {
424+ ( Value :: String ( first) , Value :: String ( second) , Value :: String ( third) ) => {
425+ Some ( Required3Validator :: compile (
426+ first. clone ( ) ,
427+ second. clone ( ) ,
428+ third. clone ( ) ,
429+ location,
430+ ) )
431+ }
432+ ( Value :: String ( _) , Value :: String ( _) , other)
433+ | ( Value :: String ( _) , other, _)
434+ | ( other, _, _) => Some ( Err ( ValidationError :: single_type_error (
435+ location. clone ( ) ,
436+ location,
437+ Location :: new ( ) ,
438+ other,
439+ JsonType :: String ,
440+ ) ) ) ,
441+ }
442+ }
443+ _ => Some ( RequiredValidator :: compile ( items, location) ) ,
444+ } ,
185445 _ => Some ( Err ( ValidationError :: single_type_error (
186446 location. clone ( ) ,
187447 location,
@@ -200,7 +460,82 @@ mod tests {
200460
201461 #[ test_case( & json!( { "required" : [ "a" ] } ) , & json!( { } ) , "/required" ) ]
202462 #[ test_case( & json!( { "required" : [ "a" , "b" ] } ) , & json!( { } ) , "/required" ) ]
463+ #[ test_case( & json!( { "required" : [ "a" , "b" , "c" ] } ) , & json!( { } ) , "/required" ) ]
203464 fn location ( schema : & Value , instance : & Value , expected : & str ) {
204465 tests_util:: assert_schema_location ( schema, instance, expected) ;
205466 }
467+
468+ // Required2Validator tests
469+ #[ test_case( & json!( { "a" : 1 , "b" : 2 } ) , true ) ]
470+ #[ test_case( & json!( { "a" : 1 , "b" : 2 , "c" : 3 } ) , true ) ]
471+ #[ test_case( & json!( { "a" : 1 } ) , false ) ]
472+ #[ test_case( & json!( { "b" : 2 } ) , false ) ]
473+ #[ test_case( & json!( { } ) , false ) ]
474+ #[ test_case( & json!( [ 1 , 2 ] ) , true ) ] // Non-object passes
475+ fn required_2 ( instance : & Value , expected : bool ) {
476+ let schema = json ! ( { "required" : [ "a" , "b" ] } ) ;
477+ let validator = crate :: validator_for ( & schema) . unwrap ( ) ;
478+ assert_eq ! ( validator. is_valid( instance) , expected) ;
479+ }
480+
481+ // Required3Validator tests
482+ #[ test_case( & json!( { "a" : 1 , "b" : 2 , "c" : 3 } ) , true ) ]
483+ #[ test_case( & json!( { "a" : 1 , "b" : 2 , "c" : 3 , "d" : 4 } ) , true ) ]
484+ #[ test_case( & json!( { "a" : 1 , "b" : 2 } ) , false ) ]
485+ #[ test_case( & json!( { "a" : 1 , "c" : 3 } ) , false ) ]
486+ #[ test_case( & json!( { "b" : 2 , "c" : 3 } ) , false ) ]
487+ #[ test_case( & json!( { } ) , false ) ]
488+ #[ test_case( & json!( "string" ) , true ) ] // Non-object passes
489+ fn required_3 ( instance : & Value , expected : bool ) {
490+ let schema = json ! ( { "required" : [ "a" , "b" , "c" ] } ) ;
491+ let validator = crate :: validator_for ( & schema) . unwrap ( ) ;
492+ assert_eq ! ( validator. is_valid( instance) , expected) ;
493+ }
494+
495+ #[ test]
496+ fn required_2_iter_errors ( ) {
497+ let schema = json ! ( { "required" : [ "a" , "b" ] } ) ;
498+ let validator = crate :: validator_for ( & schema) . unwrap ( ) ;
499+
500+ // Missing both
501+ let instance = json ! ( { } ) ;
502+ let errors: Vec < _ > = validator. iter_errors ( & instance) . collect ( ) ;
503+ assert_eq ! ( errors. len( ) , 2 ) ;
504+
505+ // Missing one
506+ let instance = json ! ( { "a" : 1 } ) ;
507+ let errors: Vec < _ > = validator. iter_errors ( & instance) . collect ( ) ;
508+ assert_eq ! ( errors. len( ) , 1 ) ;
509+
510+ // All present
511+ let instance = json ! ( { "a" : 1 , "b" : 2 } ) ;
512+ let errors: Vec < _ > = validator. iter_errors ( & instance) . collect ( ) ;
513+ assert ! ( errors. is_empty( ) ) ;
514+ }
515+
516+ #[ test]
517+ fn required_3_iter_errors ( ) {
518+ let schema = json ! ( { "required" : [ "a" , "b" , "c" ] } ) ;
519+ let validator = crate :: validator_for ( & schema) . unwrap ( ) ;
520+
521+ // Missing all
522+ let instance = json ! ( { } ) ;
523+ let errors: Vec < _ > = validator. iter_errors ( & instance) . collect ( ) ;
524+ assert_eq ! ( errors. len( ) , 3 ) ;
525+
526+ // Missing two
527+ let instance = json ! ( { "a" : 1 } ) ;
528+ let errors: Vec < _ > = validator. iter_errors ( & instance) . collect ( ) ;
529+ assert_eq ! ( errors. len( ) , 2 ) ;
530+
531+ // Missing one
532+ let instance = json ! ( { "a" : 1 , "b" : 2 } ) ;
533+ let errors: Vec < _ > = validator. iter_errors ( & instance) . collect ( ) ;
534+ assert_eq ! ( errors. len( ) , 1 ) ;
535+
536+ // All present
537+ let instance = json ! ( { "a" : 1 , "b" : 2 , "c" : 3 } ) ;
538+ let errors: Vec < _ > = validator. iter_errors ( & instance) . collect ( ) ;
539+ assert ! ( errors. is_empty( ) ) ;
540+ }
206541}
0 commit comments