@@ -240,6 +240,121 @@ def test_oneof_required(self):
240240 assert result is None
241241
242242
243+ @pytest .mark .parametrize ('schema_type' , [
244+ 'oneOf' , 'anyOf' , 'allOf' ,
245+ ])
246+ def test_oneof_discriminator (self , schema_type ):
247+ # We define a few components schemas
248+ components = {
249+ "MountainHiking" : {
250+ "type" : "object" ,
251+ "properties" : {
252+ "discipline" : {
253+ "type" : "string" ,
254+ # we allow both the explicitely matched mountain_hiking discipline
255+ # and the implicitely matched MoutainHiking discipline
256+ "enum" : ["mountain_hiking" , "MountainHiking" ]
257+ },
258+ "length" : {
259+ "type" : "integer" ,
260+ }
261+ },
262+ "required" : ["discipline" , "length" ]
263+ },
264+ "AlpineClimbing" : {
265+ "type" : "object" ,
266+ "properties" : {
267+ "discipline" : {
268+ "type" : "string" ,
269+ "enum" : ["alpine_climbing" ]
270+ },
271+ "height" : {
272+ "type" : "integer" ,
273+ },
274+ },
275+ "required" : ["discipline" , "height" ]
276+ },
277+ "Route" : {
278+ # defined later
279+ }
280+ }
281+ components ['Route' ][schema_type ] = [
282+ {"$ref" : "#/components/schemas/MountainHiking" },
283+ {"$ref" : "#/components/schemas/AlpineClimbing" },
284+ ]
285+
286+ # Add the compoments in a minimalis schema
287+ schema = {
288+ "$ref" : "#/components/schemas/Route" ,
289+ "components" : {
290+ "schemas" : components
291+ }
292+ }
293+
294+ if schema_type != 'allOf' :
295+ # use jsonschema validator when no discriminator is defined
296+ validator = OAS30Validator (schema , format_checker = oas30_format_checker )
297+ with pytest .raises (ValidationError , match = "is not valid under any of the given schemas" ):
298+ validator .validate ({
299+ "something" : "matching_none_of_the_schemas"
300+ })
301+ assert False
302+
303+ if schema_type == 'anyOf' :
304+ # use jsonschema validator when no discriminator is defined
305+ validator = OAS30Validator (schema , format_checker = oas30_format_checker )
306+ with pytest .raises (ValidationError , match = "is not valid under any of the given schemas" ):
307+ validator .validate ({
308+ "something" : "matching_none_of_the_schemas"
309+ })
310+ assert False
311+
312+ discriminator = {
313+ "propertyName" : "discipline" ,
314+ "mapping" : {
315+ "mountain_hiking" : "#/components/schemas/MountainHiking" ,
316+ "alpine_climbing" : "#/components/schemas/AlpineClimbing" ,
317+ }
318+ }
319+ schema ['components' ]['schemas' ]['Route' ]['discriminator' ] = discriminator
320+
321+ # Optional: check we return useful result when the schema is wrong
322+ validator = OAS30Validator (schema , format_checker = oas30_format_checker )
323+ with pytest .raises (ValidationError , match = "does not contain discriminating property" ):
324+ validator .validate ({
325+ "something" : "missing"
326+ })
327+ assert False
328+
329+ # Check we get a non-generic, somehow usable, error message when a discriminated schema is failing
330+ with pytest .raises (ValidationError , match = "'bad_string' is not of type integer" ):
331+ validator .validate ({
332+ "discipline" : "mountain_hiking" ,
333+ "length" : "bad_string"
334+ })
335+ assert False
336+
337+ # Check explicit MountainHiking resolution
338+ validator .validate ({
339+ "discipline" : "mountain_hiking" ,
340+ "length" : 10
341+ })
342+
343+ # Check implicit MountainHiking resolution
344+ validator .validate ({
345+ "discipline" : "MountainHiking" ,
346+ "length" : 10
347+ })
348+
349+ # Check non resolvable implicit schema
350+ with pytest .raises (ValidationError , match = "reference '#/components/schemas/other' could not be resolved" ):
351+ result = validator .validate ({
352+ "discipline" : "other"
353+ })
354+ assert False
355+
356+
357+
243358class TestOAS31ValidatorValidate (object ):
244359 @pytest .mark .parametrize ('schema_type' , [
245360 'boolean' , 'array' , 'integer' , 'number' , 'string' ,
0 commit comments