@@ -165,12 +165,26 @@ pub struct ObjectSchema {
165
165
///
166
166
/// Omitting this keyword has the same assertion behavior as an empty schema.
167
167
///
168
+ /// This keyword can be either:
169
+ /// - A boolean value: `false` means no additional items allowed, `true` means any additional items allowed
170
+ /// - A schema object: validates all additional items against this schema
171
+ ///
168
172
/// See <https://json-schema.org/draft/2020-12/json-schema-core#name-items>.
169
173
#[ serde( skip_serializing_if = "Option::is_none" ) ]
170
- pub items : Option < Box < ObjectOrReference < ObjectSchema > > > ,
174
+ pub items : Option < Box < Schema > > ,
175
+
176
+ /// Validation succeeds if each element of the instance validates against the
177
+ /// schema at the same position, if any.
178
+ ///
179
+ /// This keyword does not constrain the length of the array.
180
+ /// If the array is longer than this keyword's value,
181
+ /// this keyword validates only the prefix of matching length.
182
+ ///
183
+ /// See <https://json-schema.org/draft/2020-12/json-schema-core#name-prefixitems>.
184
+ #[ serde( rename = "prefixItems" , default , skip_serializing_if = "Vec::is_empty" ) ]
185
+ pub prefix_items : Vec < ObjectOrReference < ObjectSchema > > ,
171
186
172
187
// TODO: missing fields
173
- // - prefixItems
174
188
// - contains
175
189
176
190
// #########################################################################
@@ -657,4 +671,179 @@ mod tests {
657
671
assert ! ( schema. discriminator. is_some( ) ) ;
658
672
assert_eq ! ( 2 , schema. discriminator. unwrap( ) . mapping. unwrap( ) . len( ) ) ;
659
673
}
674
+
675
+ #[ test]
676
+ fn prefix_items_basic ( ) {
677
+ let spec = indoc:: indoc! { "
678
+ type: array
679
+ prefixItems:
680
+ - type: string
681
+ - $ref: '#/components/schemas/Age'
682
+ - type: integer
683
+ " } ;
684
+ let schema = serde_yaml:: from_str :: < ObjectSchema > ( spec) . unwrap ( ) ;
685
+
686
+ assert_eq ! ( schema. prefix_items. len( ) , 3 ) ;
687
+
688
+ // Check first schema (inline)
689
+ if let ObjectOrReference :: Object ( first_schema) = & schema. prefix_items [ 0 ] {
690
+ assert_eq ! (
691
+ first_schema. schema_type,
692
+ Some ( TypeSet :: Single ( Type :: String ) )
693
+ ) ;
694
+ } else {
695
+ panic ! ( "Expected inline schema for first prefixItems element" ) ;
696
+ }
697
+
698
+ // Check second schema (reference)
699
+ if let ObjectOrReference :: Ref { ref_path } = & schema. prefix_items [ 1 ] {
700
+ assert_eq ! ( ref_path, "#/components/schemas/Age" ) ;
701
+ } else {
702
+ panic ! ( "Expected reference for second prefixItems element" ) ;
703
+ }
704
+
705
+ // Check third schema (inline)
706
+ if let ObjectOrReference :: Object ( third_schema) = & schema. prefix_items [ 2 ] {
707
+ assert_eq ! (
708
+ third_schema. schema_type,
709
+ Some ( TypeSet :: Single ( Type :: Integer ) )
710
+ ) ;
711
+ } else {
712
+ panic ! ( "Expected inline schema for third prefixItems element" ) ;
713
+ }
714
+ }
715
+
716
+ #[ test]
717
+ fn prefix_items_with_items ( ) {
718
+ let spec = indoc:: indoc! { "
719
+ type: array
720
+ prefixItems:
721
+ - type: string
722
+ items:
723
+ type: number
724
+ " } ;
725
+ let schema = serde_yaml:: from_str :: < ObjectSchema > ( spec) . unwrap ( ) ;
726
+
727
+ assert_eq ! ( schema. prefix_items. len( ) , 1 ) ;
728
+ assert ! ( schema. items. is_some( ) ) ;
729
+
730
+ // Check prefixItems
731
+ if let ObjectOrReference :: Object ( prefix_schema) = & schema. prefix_items [ 0 ] {
732
+ assert_eq ! (
733
+ prefix_schema. schema_type,
734
+ Some ( TypeSet :: Single ( Type :: String ) )
735
+ ) ;
736
+ } else {
737
+ panic ! ( "Expected inline schema for prefixItems element" ) ;
738
+ }
739
+
740
+ // Check items
741
+ if let Some ( items_box) = & schema. items {
742
+ if let Schema :: Object ( obj_ref) = items_box. as_ref ( ) {
743
+ if let ObjectOrReference :: Object ( items_schema) = obj_ref. as_ref ( ) {
744
+ assert_eq ! (
745
+ items_schema. schema_type,
746
+ Some ( TypeSet :: Single ( Type :: Number ) )
747
+ ) ;
748
+ } else {
749
+ panic ! ( "Expected inline schema for items" ) ;
750
+ }
751
+ } else {
752
+ panic ! ( "Expected object schema for items" ) ;
753
+ }
754
+ } else {
755
+ panic ! ( "Expected items to be present" ) ;
756
+ }
757
+ }
758
+
759
+ #[ test]
760
+ fn prefix_items_empty ( ) {
761
+ let spec = indoc:: indoc! { "
762
+ type: array
763
+ prefixItems: []
764
+ " } ;
765
+ let schema = serde_yaml:: from_str :: < ObjectSchema > ( spec) . unwrap ( ) ;
766
+
767
+ assert_eq ! ( schema. prefix_items. len( ) , 0 ) ;
768
+ }
769
+
770
+ #[ test]
771
+ fn prefix_items_serialization_round_trip ( ) {
772
+ let spec = indoc:: indoc! { "
773
+ type: array
774
+ prefixItems:
775
+ - type: string
776
+ minLength: 5
777
+ - type: integer
778
+ minimum: 0
779
+ items:
780
+ type: boolean
781
+ " } ;
782
+
783
+ // Deserialize from YAML
784
+ let original = serde_yaml:: from_str :: < ObjectSchema > ( spec) . unwrap ( ) ;
785
+
786
+ // Serialize to YAML
787
+ let serialized = serde_yaml:: to_string ( & original) . unwrap ( ) ;
788
+
789
+ // Deserialize back
790
+ let round_tripped = serde_yaml:: from_str :: < ObjectSchema > ( & serialized) . unwrap ( ) ;
791
+
792
+ // Compare structures (not YAML strings)
793
+ assert_eq ! ( original, round_tripped) ;
794
+ }
795
+
796
+ #[ test]
797
+ fn items_object_schema_still_works ( ) {
798
+ let spec = indoc:: indoc! { "
799
+ type: array
800
+ items:
801
+ type: string
802
+ minLength: 5
803
+ " } ;
804
+ let schema = serde_yaml:: from_str :: < ObjectSchema > ( spec) . unwrap ( ) ;
805
+
806
+ assert ! ( schema. items. is_some( ) ) ;
807
+
808
+ if let Some ( items) = & schema. items {
809
+ match items. as_ref ( ) {
810
+ Schema :: Object ( obj_ref) => {
811
+ if let ObjectOrReference :: Object ( items_schema) = obj_ref. as_ref ( ) {
812
+ assert_eq ! (
813
+ items_schema. schema_type,
814
+ Some ( TypeSet :: Single ( Type :: String ) )
815
+ ) ;
816
+ assert_eq ! ( items_schema. min_length, Some ( 5 ) ) ;
817
+ } else {
818
+ panic ! ( "Expected inline schema" ) ;
819
+ }
820
+ }
821
+ _ => panic ! ( "Expected object schema for items" ) ,
822
+ }
823
+ } else {
824
+ panic ! ( "Expected items to be present" ) ;
825
+ }
826
+ }
827
+
828
+ #[ test]
829
+ fn items_boolean_serialization_round_trip ( ) {
830
+ let spec = indoc:: indoc! { "
831
+ type: array
832
+ prefixItems:
833
+ - type: string
834
+ items: false
835
+ " } ;
836
+
837
+ // Deserialize from YAML
838
+ let original = serde_yaml:: from_str :: < ObjectSchema > ( spec) . unwrap ( ) ;
839
+
840
+ // Serialize to YAML
841
+ let serialized = serde_yaml:: to_string ( & original) . unwrap ( ) ;
842
+
843
+ // Deserialize back
844
+ let round_tripped = serde_yaml:: from_str :: < ObjectSchema > ( & serialized) . unwrap ( ) ;
845
+
846
+ // Compare structures (not YAML strings)
847
+ assert_eq ! ( original, round_tripped) ;
848
+ }
660
849
}
0 commit comments