Validating multiple subsequent requests with "discriminator" in the same validator instance behaves incorrectly

Issue #46 resolved
Tuan Dinh created an issue

Hi,

The validation result becomes inconsistent when validating schema involves "discirminator" field. Take the following json:

{
  "swagger": "2.0",
  "basePath": "/",
  "paths": {
       "/create": {
        "post": {
            "consumes": [
                "application/json"
            ],
            "parameters": [
                {
                    "$ref": "#/parameters/petRequest"
                }
            ],
            "responses": {
                "201": {
                    "$ref": "#/responses/petResponse"
                }    
            }
         }
        } 
  },
  "definitions": {
    "Pet": {
      "type": "object",
      "discriminator" : "petType",
      "properties": {
        "name": {
          "type": "string"
        },
        "petType": {
          "type": "string"
        }
      },
      "required": [
        "name",
        "petType"
      ]
    },
    "Dog": {
      "allOf": [
        {
          "$ref": "#/definitions/Pet"
        },
        {
          "type": "object",
          "properties": {
            "packSize": {
              "type": "integer",
              "format": "int32"
            }
          },
          "required": [
            "packSize"
          ]
        }
      ]
    }
  },
  "createPetResponse": {
        "type": "object"
  },
  "parameters": {
      "petRequest": {
        "name": "petRequest",
        "in": "body",
        "required": true,
        "schema": {
            "$ref": "#/definitions/Pet"
        }
    }
  },
  "responses": {
    "petResponse": {
        "schema": {
            "$ref": "#/definitions/createPetResponse"
        }
    }
  }
}

where "Pet" schema has the attribute "discriminator"

"discriminator" : "petType"

So if I send the following request (with wrong packSize value, should be an int32):

    @Test
    public void singleRequest() {
        final Request request = SimpleRequest.Builder.post("/create")
                .withBody("{\"name\":\"Kiki\",\"petType\":\"Dog\",\"packSize\":\"not-a-number\"}")
                .build();
        final ValidationReport report = validator.validateRequest(request);
        assertTrue(report.hasErrors());
    }

Test passes and report has key & message:

Key: validation.schema.discriminator : Failed validation of discriminator schema '/definitions/Dog'

It's not clear what exactly failing when validating "Dog", but let's accept it for now.

Now, if we send two identical requests back to back:

    @Test
    public void doubleRequestsWithoutValidatorRecreation() {
        final Request request1 = SimpleRequest.Builder.post("/create")
                .withBody("{\"name\":\"Kiki\",\"petType\":\"Dog\",\"packSize\":\"not-a-number\"}")
                .build();
        final ValidationReport report1 = validator.validateRequest(request1);
        assertTrue(report1.hasErrors());

        final Request request2 = SimpleRequest.Builder.post("/create")
                .withBody("{\"name\":\"Kiki\",\"petType\":\"Dog\",\"packSize\":\"not-a-number\"}")
                .build();
        final ValidationReport report2 = validator.validateRequest(request2);
        assertTrue(report2.hasErrors());
    }

In theory, two requests should fail with the same validation error, but the second request went through, this test fails here:

        assertTrue(report2.hasErrors());

Interestingly, if take this third test:

    @Test
    public void doubleRequestsWithValidatorRecreation() {
        final Request request1 = SimpleRequest.Builder.post("/create")
                .withBody("{\"name\":\"Kiki\",\"petType\":\"Dog\",\"packSize\":\"not-a-number\"}")
                .build();
        final ValidationReport report1 = validator.validateRequest(request1);
        assertTrue(report1.hasErrors());

        recreateValidator();

        final Request request2 = SimpleRequest.Builder.post("/create")
                .withBody("{\"name\":\"Kiki\",\"petType\":\"Dog\",\"packSize\":\"not-a-number\"}")
                .build();
        final ValidationReport report2 = validator.validateRequest(request2);
        assertTrue(report2.hasErrors());
    }

then it passes, which mean the validator fails both requests (expected).

recreateValidator:

    public void recreateValidator() {
        validator =  SwaggerRequestResponseValidator.createFor("/oai/pet-api.json")
                 .withLevelResolver(
                        LevelResolver.create()
                        .withLevel("validation.schema.additionalProperties", ValidationReport.Level.IGNORE)
                        .build())
                .build();
    }

To me, the behaviour of the second test is an issue. Eventhough, there is a workaround as the third one suggests, but it may be expensive to create the validator for every request ?

I don't want to speculate but it seems once it fails to validate the schema specified in "discriminator", it ignores that schema in future validation ?

Or perhaps, there is something about the validation that I dont understand here.

Either way, I'd love an explanation.

Thanks, Tuan

Comments (8)

  1. James Navin

    Thanks Tuan. I'll try to take a look at this next week.

    It is definitely a bug if there is some sort of state being maintained between validation executions.

  2. James Navin

    Actually - I know what's going on. During validation we remove the discriminator keyword to avoid a validation loop, and then never put it back in (facepalm), so subsequent requests never see the discriminator keyword and so never validate against it.

    Removal: https://bitbucket.org/atlassian/swagger-request-validator/src/3e529c57bc0de5815597336c27cea4850ce31d0e/swagger-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/SwaggerV20Library.java#SwaggerV20Library.java-291

    Check-and-bail: https://bitbucket.org/atlassian/swagger-request-validator/src/3e529c57bc0de5815597336c27cea4850ce31d0e/swagger-request-validator-core/src/main/java/com/atlassian/oai/validator/schema/SwaggerV20Library.java#SwaggerV20Library.java-230

    I think the quickest fix is probably to re-add the keyword before leaving the validate method, but this probably has problems with multiple threads using the validator at the same time. Ideally we could use a thread-safe way to detect the validation cycle (maybe threadlocal storage or something like that).

  3. Tuan Dinh reporter

    Thanks James,

    It explains a lot. Why do we have to remove the keyword again ? If it results in a validation loop, is that more towards the schema is wrongly configured ?

    Can we make "a copy" of the schema for each validation method while keeping the original unchanged ?

    Anyway, I'm throwing stuff out without much context/understanding what the validator is doing around that. Just wondering how do we coordinate the effort to resolve this issue.

    Cheers, Tuan

  4. Log in to comment