Validation of primitive types with allOf and resolveCombinators=true does not honor OpenAPI specification

Issue #381 new
Former user created an issue

allOf validation does not work in some use cases. Follows our internal analysis (sorry I am not sure how to make it shorter):

Our use case is to have general schema definition with general description. Then reference this schema for a field in response/request object and update the description to be more precise for given use case.

Solution depends on OpenAPI specification (OAS) version.

First logical approach would be this definition (A), thinking that we get all schema details from reference and description will get overridden:

username:
  description: Customer username. If it is empty, it is filled by email address by default.
  $ref: '#/components/schemas/Username'

Funny thing - many people think exactly like this (seeing all the questions about this topic throughout the internet) but it is not possible in OAS (continue reading for clarification).

OAS up to 3.0.3 defines that if there is a reference in schema definition, only details from that referenced schema are used, ignoring anything else. If written like this though, most of the tools survive (validation is ok, code generation is ok but only the general description from referenced schema is used, swagger editor valid with warning) - extra stuff outside the reference is just ignored, as OAS states.

Recently OAS 3.1.0 was released which allows to override the description exactly like this (A), see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#referenceObject . But it is not yet supported in codegen, atlassian validator and not even in swagger editor.

So we cannot use OAS 3.1.0 and need to stick with 3.0.3 for now. To be valid, we need to use the allOf approach (B):

username:
  description: Customer username. If it is empty, it is filled by email address by default.
  allOf:
  - $ref: '#/components/schemas/Username'

FYI Username is a string schema:

    Username:
      description: User name.
      type: string
      maxLength: 255

Codegen works as expected, using the overridden description in DTO javadoc.

Second problem with allOf during validation is the schema validation "one by one" behavior. For example we have an object schema definition where we combine product properties with xx* properties for project extensibility:

RegistrationRequest:
  type: object
  description: Customer registration request.
  allOf:
    - properties:
        username:
          description: Customer username. If it is empty, it is filled by email address by default.
          allOf:
          - $ref: '#/components/schemas/Username'
    - $ref: '#/components/schemas/XXCustom30'

When we try to validate following object, we logically expect it to be valid.

{
    "username": "tc1",
    "xxCustom01": "xxCustom01"
}

But validation fails with this error:

[ERROR][REQUEST][POST /services/rest/customer-profile/v1/profiles @body] Instance failed to match all required schemas (matched only 0 out of 2)
    * /components/schemas/RegistrationRequest/allOf/0: Object instance has properties which are not allowed by the schema: ["xxCustom01"]
    * /components/schemas/RegistrationRequest/allOf/1: Object instance has properties which are not allowed by the schema: ["username"] 
    - [ERROR][] Object instance has properties which are not allowed by the schema: ["xxCustom01"]  
    - [ERROR][] Object instance has properties which are not allowed by the schema: ["username"]

This is because of the one by one behavior (defined in OAS). First the validation checks the first schema - username is ok but HA! we don't allow the xxCustom01 property here so validation fails. Lets also check the second schema - xxCustom01 is ok but HA! we don't allow the username property here so validation fails. This is ok accorfing to specs.

Advices on the internet (also from Atlassian validator devs) say to allow additionalProperties for allOf (validator config, see https://bitbucket.org/atlassian/swagger-request-validator/src/swagger-request-validator-2.27.0/docs/FAQ.md ). That would validate the properties defined in the schemas one by one and ignore validation for any properties not described by current schema. That is not acceptable for our use case. We want to be strict because any not described property might be potentially malicious to send it from internet to the backend (API in our API Gateway is a subset of API on backend).

To resolve this problem, there is fortunately another validator config ResolveFully=true with implicit ResolveCombinators=true. This option takes the allOf (and other combinators) and gathers properties from all the schemas into one composed schema (see config options here https://github.com/swagger-api/swagger-parser#options ) Then the validation behavior is like we want it - for incoming object all properties are validated at once and no unknown properties are allowed. So this solves our second problem but reintroduces our problem with overriding description for a primitive type where Atlassian validator fails with error like this:

[ERROR][REQUEST][POST /services/rest/customer-profile/v1/profiles @body] [Path '/username'] Instance type (string) does not match any allowed primitive type (allowed: ["object"])

The validator is written in a way that references only work for object schemas and not for primitive schemas (like e.g. string schema here). After long research of OAS and discussions about how allOf combinator (and others) is specified and intended to be used, this validation behavior is a clear violation of the OAS. By definition of allOf combinator (see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schemaComposition and https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/ and https://community.smartbear.com/t5/Swagger-Open-Source-Tools/allOf-with-same-property-names/m-p/203929 ), it is a list of schemas (local or referenced) validated object needs to be valid against "one by one". Which means that during validation, object needs to be valid against each of the listed schemas, validated one by one. Together with the ResolveCombinators=true configuration, the validator creates new general schema, defaults to type object and takes only properties (+ required properties names) into the new schema. But since we have primitive string schema, there are no properties and all other schema details are ignored. Hence the validation error message (we sent string but validator expects object). See also this discussion about the same sort of problem here https://community.smartbear.com/t5/ReadyAPI-Questions/v3-0-bug-swagger-compliance-assertion-fails-in-v3-0-but-is-ok-in/m-p/194796#M46122

Some related validator code in version 2.27.0: * adding type:object as default - io.swagger.v3.parser.util.ResolverFully.resolveSchema(Schema):435 * aggregating allOf - io.swagger.v3.parser.util.ResolverFully.resolveSchema(Schema):386

Also this issue describes in more detail the same problem in issue https://bitbucket.org/atlassian/swagger-request-validator/issues/372 so it can be closed.

Comments (0)

  1. Log in to comment