Validation Failing For Swagger with Inheritance and Polymorphism (including both oneOf & allOf)

Issue #315 open
Shubham Mahindru created an issue

We are trying to validate an API which includes a set of addresses described by using oneOf keyword and then followed by discriminator & mapping. The mapping fields include values with allOf Keyword.

The swagger document is rendered fine on Swagger UI, anyhow the actual request for validating the swagger fails with the attached error.

URI : /bitBucketIssue/v1/user/v1/accountregister/

BaseURI: /bitBucketIssue/v1/

SwaggerURL(which is to be validated): user/v1/accountregister/

PFA ValidationReport, Error details, YAML file & Request used to validate the document.

Comments (19)

  1. Shubham Mahindru reporter

    Hi James,

    Did you get some time to check for the issue? Please check once for the issue, we are totally blocked and can’t proceed further without a permanent fix

  2. Shubham Mahindru reporter

    This FAQ suggestion was already been considered and implemented, we have programmatically opted to ignore the additional properties.
    PFB the attachment.

  3. Amon Amon

    Using:

    <groupId>com.atlassian.oai</groupId>
            <artifactId>swagger-request-validator-core</artifactId>
            <version>2.11.0</version>
    

    And this validator:

    OpenApiInteractionValidator validator = OpenApiInteractionValidator
    .createFor(OPENAPI_LOCATION)
    .withLevelResolver(LevelResolverFactory.withAdditionalPropertiesIgnored())
    .build();

    I am getting the following error in your situation:

    '/personal_data/addresses/0'] Instance failed to match exactly one schema (matched 3 out of 3)
    com.nn.aav.insurance.nonlife.insurancepackage.business.validation.boundary.OpenAPIValidationException: Validation failed.
    [ERROR][REQUEST][POST user/v1/accountregister @body] [Path '/personal_data/addresses/0'] Instance failed to match exactly one schema (matched 3 out of 3)

    Which is correct because it can match all of the items.

    You can do two things:

    1. Dont use the AbstractAddress. So copy it to each child. So you can set additionalProperties: false.
    2. Another thing you can do which is very easy to do is make a property required in each child which is unique.

    So in the last example:

    {
    "personal_data": {
    "firstName": "Demo",
    "lastName": "Demo",
    "addresses": [
    {
    "country": "Demo",
    "type": "ADDRESS_TYPE:v3.address.GermanAddress",
    "usageType": "ADDRESS_USAGE_TYPE:HOME_ADDRESS",
    "primary": true,
    "uuid": "123456789",
    "addressAddition": "",
    "cityGeneral": "Demo",
    "housenumber": "1234567",
    "street": "Demo",
    "zipCode": "12345678",
    "name": "Demo"
    }
    ]
    },
    "country": "Demo"
    }

    Using swagger:

    {
      "openapi": "3.0.2",
      "info": {
        "title": "Account",
        "version": "1.0.0"
      },
      "paths": {
        "/user/v1/accountregister/": {
          "post": {
            "tags": [
              "Personal Data"
            ],
            "summary": "Store a new account",
            "description": "",
            "operationId": "registerAccount",
            "requestBody": {
              "$ref": "#/components/requestBodies/RegisterUserModel"
            },
            "responses": {
              "201": {
                "description": "Account registered successfully.",
                "content": {
                  "application/json": {
                    "examples": {
                      "response": {
                        "value": {
                          "hasError": false,
                          "errorCategory": "",
                          "errorList": null
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "components": {
        "requestBodies": {
          "RegisterUserModel": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegisterUserModel"
                }
              }
            },
            "required": true
          }
        },
        "schemas": {
          "AbstractAddress": {
            "description": "Each of the concrete Addresses have the following attributes in common.",
            "type": "object",
            "required": [
              "type",
              "country",
              "usageType",
              "primary",
              "uuid"
            ],
            "properties": {
              "country": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "usageType": {
                "type": "string"
              },
              "primary": {
                "type": "boolean"
              },
              "uuid": {
                "type": "string"
              }
            }        
          },
          "GeneralAddress": {
            "description": "General address, used for Countries which do not have a specialized address format.",
            "required": [
              "cityGeneral"         
             ],
            "allOf": [
              {
                "$ref": "#/components/schemas/AbstractAddress"
              },
              {
                "type": "object",
                "properties": {
                  "addressAddition": {
                    "type": "string",
                    "nullable": true
                  },
                  "cityGeneral": {
                    "type": "string",
                    "nullable": true
                  },
                  "housenumber": {
                    "type": "string",
                    "nullable": true
                  },
                  "street": {
                    "type": "string",
                    "nullable": true
                  },
                  "zipCode": {
                    "type": "string",
                    "nullable": true
                  },
                  "name": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            ]
    
          },
          "AustralianAddress": {
            "description": "Address format for Australian addresses.",
             "required": [
              "cityAussie"         
             ],
            "allOf": [
              {
                "$ref": "#/components/schemas/AbstractAddress"
              },
              {
                "type": "object",
                "properties": {
                  "addressLine1": {
                    "type": "string",
                    "nullable": true
                  },
                  "addressLine2": {
                    "type": "string",
                    "nullable": true
                  },
                  "state": {
                    "type": "string",
                    "nullable": true
                  },
                  "cityAussie": {
                    "type": "string",
                    "nullable": true
                  },
                  "zipCode": {
                    "type": "string",
                    "nullable": true
                  },
                  "name": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            ]
          },
          "HongKongAddress": {
            "description": "Address format for addresses located in Hong Kong",
            "required": [
              "addressLine3"         
             ],
            "allOf": [
              {
                "$ref": "#/components/schemas/AbstractAddress"
              },
              {
                "type": "object",
                "properties": {
                  "addressLine1": {
                    "type": "string",
                    "nullable": true
                  },
                  "addressLine2": {
                    "type": "string",
                    "nullable": true
                  },
                  "addressLine3": {
                    "type": "string",
                    "nullable": true
                  },
                  "name": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            ]
          },
          "PersonalData": {
            "description": "Data of a single user profile",
            "type": "object",
            "required": [
              "firstName",
              "lastName"
            ],
            "properties": {
              "firstName": {
                "description": "The given name of a person.",
                "type": "string",
                "nullable": true
              },
              "lastName": {
                "description": "The surname of a person.",
                "type": "string",
                "nullable": true
              },
              "addresses": {
                "description": "The list of addresses",
                "type": "array",
                "items": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GeneralAddress"
                    },
                    {
                      "$ref": "#/components/schemas/AustralianAddress"
                    },
                    {
                      "$ref": "#/components/schemas/HongKongAddress"
                    }
                  ],
                  "discriminator": {
                    "propertyName": "type",
                    "mapping": {
                      "ADDRESS_TYPE:v3.address.AustralianAddress": "#/components/schemas/AustralianAddress",
                      "ADDRESS_TYPE:v3.address.HongKongAddress": "#/components/schemas/HongKongAddress",
                      "ADDRESS_TYPE:v3.address.GermanAddress": "#/components/schemas/GeneralAddress"
                    }
                  }
                }
              }
            }
          },
          "RegisterUserModel": {
            "type": "object",
            "required": [
              "country",
              "personal_data"
            ],
            "properties": {
              "personal_data": {
                "$ref": "#/components/schemas/PersonalData"
              },
              "country": {
                "type": "string",
                "nullable": false,
                "description": "Country to which user belongs."
              }
            }
          }
        }
      }
    }
    

  4. James Navin

    A fix to the related problem #336 has been released in v2.19.3.

    Please re-open this issue if the problem persists in the latest version.

    Thanks.

  5. Shubham Mahindru reporter

    Hi James,
    The issue still exists, I have tried with 2.12.0, 2.19.3 & 2.19.4, getting the same error again.

    Please have a look once.

  6. Shubham Mahindru reporter

    Hi James/Team,

    The issue still exists, it still fails with the same exception as attached, tested with the latest version. Please check once.

  7. Full Name

    @Shubham Mahindru We momentaly solved by stripping off the AdditionalPropertiesInjectionTransformer out of the list of the SchemaValidator transformer, forking the library and making the list of transformers public.

  8. Shubham Mahindru reporter

    Hi
    Can you please share what is the intended purpose of the code change ?
    This is the following code which you are mentioning, so what other impacts would it have on the validator, because with the latest version even the problem exists.

    private final List<SchemaTransformer> transformers = Arrays.asList(SchemaDefinitionsInjectionTransformer.getInstance(), SchemaRefInjectionTransformer.getInstance(), AdditionalPropertiesInjectionTransformer.getInstance(), RequiredFieldTransformer.getInstance());

  9. Shubham Mahindru reporter

    Hi,

    With the changes suggested, now its matching multiple schemas

    { "error": "400",

    ‌ "error_description": "Validation failed.\n[ERROR][REQUEST][POST /check3/v1/account/v1/register @body] [Path '/personal_data/addresses/0'] Instance failed to match exactly one schema (matched 9 out of 9)"

    }

    Please suggest as it still complains about the validation part.

  10. Sri Venkata Kartik Naidhruva

    Hi,

    Any Update on this. We are still facing issues with the latest releases.

  11. Sri Venkata Kartik Naidhruva

    Hi Guys,

    Please help us understand If this will be delivered in sometime future and ETA on that please.

  12. James Navin

    Hi Sri,

    Unfortunately I can’t give a commitment on when a fix to this specific problem will be available. If you could include example spec + request for your scenario it would help. I am also more than happy to review PRs if you are able to identify and fix the cause.

    Cheers.

  13. Reto Urfer

    Hi

    I guess i run into the same problem at least with allOf. It seems that the two scmeas are not merge into one before the validation when i interpret the error message correctly.

    2022-09-24 11:36:47.129 ERROR 22248 --- [qtp373973695-40] c.a.o.v.s.DefaultValidationReportHandler : OpenAPI location=REQUEST key=POST#/private/v1/oa3/test levels=ERROR messages=Validation failed.
    [ERROR][REQUEST][POST /private/v1/oa3/test @body] [Path '/obj'] Instance failed to match all required schemas (matched only 0 out of 2)
        * /properties/obj/allOf/0: Object instance has properties which are not allowed by the schema: ["_type","str"]
        * /properties/obj/allOf/1: Object instance has properties which are not allowed by the schema: ["base"] 
        - [ERROR][] [Path '/obj'] Object instance has properties which are not allowed by the schema: ["_type","str"]   
        - [ERROR][] [Path '/obj'] Object instance has properties which are not allowed by the schema: ["base"]
    

    The OpenApi Spec is quite simple

    openapi: 3.0.0
    info:
      title: OA3 Validation Test Service
      version: "1.0"
    servers:
      - url: "http://localhost:8080/oa3"
    components:
      schemas:
        TestRequest:
          type: object
          required:
            - obj
          properties:
            obj:
              $ref: '#/components/schemas/ObjectA'
        BaseObject:
          type: object
          required:
            - base
          properties:
            base:
              type: string
        ObjectA:
          allOf:
            - $ref: '#/components/schemas/BaseObject'
            - type: object
              required:
                - _type
                - str
              properties:
                _type:
                  type: string
                str:
                  type: string
        TestResponse:
          type: object
          required:
            - value
          properties:
            value:
              type: string
    paths:
      /test:
        post:
          operationId: test
          requestBody:
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/TestRequest'
          responses:
            200:
              description: OK with the result of the executed query
              content:
                application/json:
                  schema:
                    $ref: '#/components/schemas/TestResponse'
    

    And here also the requets body:

    {
        "obj": {
            "base": "test base",
            "_type": "OBJECT_A",
            "str": "12345"
        }
    }
    

    Hope this helps to identify the problem

  14. Patrick Boos

    Also struggled with this. For us the following setting made allOf (similar setup as Reto Urfer) work:

    .withResolveCombinators(true)
    

    This also seems to be the recommended approach mentioned in the FAQ.

    Still it would be nice if it would work without it :). As I am not sure if this has any side effects.

  15. Patrick Boos

    The problem we are still experiencing is: Instance failed to match exactly one schema (matched 6 out of 6).

  16. Reto Urfer

    i did some analysis and it seems that the problems with oneOf are completely different from the problems with allOf. Like Patrick Boos mentioned, the problems with allOf can be solved be defining .withResolveCombinators(true). The problems with oneOf are described in the following issues #289, #334 and #349. All of them are still open and marked as major problems

  17. Log in to comment