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