@@ -820,3 +820,239 @@ func TestSchemaValidator_ValidateSchemaBytesWithVersion_NilSchema(t *testing.T)
820820 assert .False (t , valid , "Should fail with nil schema" )
821821 assert .Empty (t , errors , "Should not return errors for nil schema" )
822822}
823+
824+ // https://github.com/daveshanley/vacuum/issues/520
825+ func TestValidateSchema_OneOf_MultipleMatches_Issue520 (t * testing.T ) {
826+ // This test reproduces the issue from vacuum #520
827+ // The example matches BOTH oneOf alternatives which should fail validation
828+ // but the error details are not being populated correctly
829+
830+ spec := `openapi: 3.1.0
831+ info:
832+ title: Test
833+ version: 1.0.0
834+ components:
835+ schemas:
836+ Test:
837+ type: object
838+ oneOf:
839+ - properties:
840+ pim:
841+ type: string
842+ - properties:
843+ pam:
844+ type: string
845+ `
846+
847+ doc , err := libopenapi .NewDocument ([]byte (spec ))
848+ assert .NoError (t , err )
849+
850+ model , errs := doc .BuildV3Model ()
851+ assert .Empty (t , errs )
852+
853+ testSchema := model .Model .Components .Schemas .GetOrZero ("Test" ).Schema ()
854+
855+ testData := map [string ]interface {}{
856+ "pam" : "nop" ,
857+ }
858+
859+ validator := NewSchemaValidator ()
860+ valid , validationErrors := validator .ValidateSchemaObject (testSchema , testData )
861+
862+ assert .False (t , valid , "validation should fail because example matches both oneOf alternatives" )
863+ assert .NotEmpty (t , validationErrors , "validation errors should be present" )
864+
865+ if len (validationErrors ) > 0 {
866+
867+ assert .NotEmpty (t , validationErrors [0 ].SchemaValidationErrors ,
868+ "SchemaValidationErrors should contain detailed error information about the oneOf violation" )
869+
870+ if len (validationErrors [0 ].SchemaValidationErrors ) > 0 {
871+ firstError := validationErrors [0 ].SchemaValidationErrors [0 ]
872+
873+ assert .Contains (t , firstError .Reason , "oneOf" ,
874+ "error should mention oneOf constraint violation" )
875+ }
876+ }
877+ }
878+
879+ // https://github.com/daveshanley/vacuum/issues/520
880+ func TestValidateSchema_OneOf_Discriminant_Valid (t * testing.T ) {
881+
882+ spec := `openapi: 3.1.0
883+ info:
884+ title: Test
885+ version: 1.0.0
886+ components:
887+ schemas:
888+ Test:
889+ type: object
890+ oneOf:
891+ - properties:
892+ type:
893+ const: pim
894+ pim:
895+ type: string
896+ required: [type, pim]
897+ - properties:
898+ type:
899+ const: pam
900+ pam:
901+ type: string
902+ required: [type, pam]`
903+
904+ doc , err := libopenapi .NewDocument ([]byte (spec ))
905+ assert .NoError (t , err )
906+
907+ model , errs := doc .BuildV3Model ()
908+ assert .Empty (t , errs )
909+
910+ testSchema := model .Model .Components .Schemas .GetOrZero ("Test" ).Schema ()
911+
912+ testData := map [string ]interface {}{
913+ "type" : "pam" ,
914+ "pam" : "nop" ,
915+ }
916+
917+ validator := NewSchemaValidator ()
918+ valid , validationErrors := validator .ValidateSchemaObject (testSchema , testData )
919+
920+ assert .True (t , valid , "validation should pass for discriminant oneOf" )
921+ assert .Empty (t , validationErrors , "no validation errors should be present" )
922+ }
923+
924+ // https://github.com/daveshanley/vacuum/issues/520
925+ func TestValidateSchema_OneOf_NoMatches (t * testing.T ) {
926+
927+ spec := `openapi: 3.1.0
928+ info:
929+ title: Test
930+ version: 1.0.0
931+ components:
932+ schemas:
933+ Test:
934+ type: object
935+ oneOf:
936+ - properties:
937+ foo:
938+ type: string
939+ required: [foo]
940+ - properties:
941+ bar:
942+ type: integer
943+ required: [bar]`
944+
945+ doc , err := libopenapi .NewDocument ([]byte (spec ))
946+ assert .NoError (t , err )
947+
948+ model , errs := doc .BuildV3Model ()
949+ assert .Empty (t , errs )
950+
951+ testSchema := model .Model .Components .Schemas .GetOrZero ("Test" ).Schema ()
952+
953+ testData := map [string ]interface {}{
954+ "baz" : "invalid" ,
955+ }
956+
957+ validator := NewSchemaValidator ()
958+ valid , validationErrors := validator .ValidateSchemaObject (testSchema , testData )
959+
960+ assert .False (t , valid , "validation should fail because example matches no oneOf alternatives" )
961+ assert .NotEmpty (t , validationErrors , "validation errors should be present" )
962+
963+ if len (validationErrors ) > 0 {
964+ assert .NotEmpty (t , validationErrors [0 ].SchemaValidationErrors ,
965+ "SchemaValidationErrors should contain detailed error information" )
966+ }
967+ }
968+
969+ // https://github.com/daveshanley/vacuum/issues/520
970+ func TestValidateSchema_OneOf_SimpleTypes (t * testing.T ) {
971+
972+ testCases := []struct {
973+ name string
974+ spec string
975+ value interface {}
976+ shouldPass bool
977+ errorDetail string
978+ }{
979+ {
980+ name : "valid string" ,
981+ spec : `openapi: 3.1.0
982+ info:
983+ title: Test
984+ version: 1.0.0
985+ components:
986+ schemas:
987+ Test:
988+ oneOf:
989+ - type: string
990+ - type: integer` ,
991+ value : "hello" ,
992+ shouldPass : true ,
993+ },
994+ {
995+ name : "valid integer" ,
996+ spec : `openapi: 3.1.0
997+ info:
998+ title: Test
999+ version: 1.0.0
1000+ components:
1001+ schemas:
1002+ Test:
1003+ oneOf:
1004+ - type: string
1005+ - type: integer` ,
1006+ value : 42 ,
1007+ shouldPass : true ,
1008+ },
1009+ {
1010+ name : "invalid - matches both (ambiguous pattern)" ,
1011+ spec : `openapi: 3.1.0
1012+ info:
1013+ title: Test
1014+ version: 1.0.0
1015+ components:
1016+ schemas:
1017+ Test:
1018+ oneOf:
1019+ - type: string
1020+ - type: string
1021+ pattern: '^[0-9]+$'` ,
1022+ value : "123" ,
1023+ shouldPass : false ,
1024+ errorDetail : "oneOf" ,
1025+ },
1026+ }
1027+
1028+ for _ , tc := range testCases {
1029+ t .Run (tc .name , func (t * testing.T ) {
1030+ doc , err := libopenapi .NewDocument ([]byte (tc .spec ))
1031+ assert .NoError (t , err )
1032+
1033+ model , errs := doc .BuildV3Model ()
1034+ assert .Empty (t , errs )
1035+
1036+ testSchema := model .Model .Components .Schemas .GetOrZero ("Test" ).Schema ()
1037+
1038+ validator := NewSchemaValidator ()
1039+ valid , validationErrors := validator .ValidateSchemaObject (testSchema , tc .value )
1040+
1041+ if tc .shouldPass {
1042+ assert .True (t , valid , "validation should pass" )
1043+ assert .Empty (t , validationErrors , "no validation errors expected" )
1044+ } else {
1045+ assert .False (t , valid , "validation should fail" )
1046+ assert .NotEmpty (t , validationErrors , "validation errors should be present" )
1047+
1048+ if len (validationErrors ) > 0 && tc .errorDetail != "" {
1049+ if len (validationErrors [0 ].SchemaValidationErrors ) > 0 {
1050+ firstError := validationErrors [0 ].SchemaValidationErrors [0 ]
1051+ assert .Contains (t , firstError .Reason , tc .errorDetail ,
1052+ "error should contain expected detail" )
1053+ }
1054+ }
1055+ }
1056+ })
1057+ }
1058+ }
0 commit comments