Fix validate pacts with openAPI 3.0.3 spec with oneOf list

Issue #94 invalid
Pablo Cabo created an issue

Is not posible to validate a pact against an openAPI spec 3.0.3 with the use of oneOf in the response. Only validates if the list of “oneOf” has one element.

schema:
                type: "object"
                oneOf:
                  - $ref: "#/components/schemas/read"
                  - $ref: "#/components/schemas/readDetail"

Simple example:
The openAPI

openapi: 3.0.3
info:
  title: example-api
  description: |
    Example Public API.
  version: 0.3.0-SNAPSHOT
  license:
    name: mylicense
    url: https://mydomain.com/license
servers:
  - url: https://mydomain.com/service

paths:
  /read/1:
    get:
      summary: "Read one item"
      operationId: "read"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: "object"
                oneOf:
                  - $ref: "#/components/schemas/read"
                  - $ref: "#/components/schemas/readDetail"

components:
  schemas:
    read:
      properties:
        number:
          type: integer
          format: int32
          example: 12
    readDetail:
      properties:
        number:
          type: integer
          format: int32
          example: 12
        remarks:
          type: string
          example: "A description"

The pact:

{
  "consumer": {
    "name": "consumer-sync"
  },
  "interactions": [
    {
      "description": "Read item",
      "request": {
        "matchingRules": {
          "path": {
            "combine": "AND",
            "matchers": [
              {
                "match": "regex",
                "regex": "/read/1"
              }
            ]
          }
        },
        "method": "GET",
        "path": "/read/1"
      },
      "response": {
        "body": {
          "number": 100
        },
        "generators": {
          "body": {
            "$.number": {
              "max": 2147483647,
              "min": 0,
              "type": "RandomInt"
            }
          }
        },
        "headers": {
          "Content-Type": "application/json"
        },
        "matchingRules": {
          "body": {
            "$.number": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "number"
                }
              ]
            }
          }
        },
        "status": 200
      }
    }
  ],
  "metadata": {
    "pact-jvm": {
      "version": "4.3.9"
    },
    "pactSpecification": {
      "version": "3.0.0"
    }
  },
  "provider": {
    "name": "provider-sync"
  }
}

The error:

1 error(s)
    response.body.incompatible: 1
0 warning(s)
{
  warnings: [],
  errors: [
    {
      code: 'response.body.incompatible',
      message: 'Response body is incompatible with the response body schema in the spec file: should match exactly one schema in oneOf',
      mockDetails: {
        interactionDescription: 'Read item',
        interactionState: '[none]',
        location: '[root].interactions[0].response.body',
        mockFile: 'pacts/consumer-sync-provider-sync.json',
        value: { number: 100 }
      },
      source: 'spec-mock-validation',
      specDetails: {
        location: '[root].paths./read/1.get.responses.200.content.application/json.schema.oneOf',
        pathMethod: 'get',
        pathName: '/read/1',
        specFile: 'openapitest.yml',
        value: [
          {
            properties: {
              number: { type: 'integer', format: 'int32', example: 12 }
            }
          },
          {
            properties: {
              number: { type: 'integer', format: 'int32', example: 12 },
              remarks: { type: 'string', example: 'A description' }
            }
          }
        ]
      },
      type: 'error'
    }
  ]
}

Comments (3)

  1. Sebastian Tello Account Deactivated

    @{557058:5c25f91c-c8f3-4a13-9254-7dfe9e35b82b} , the library seems to be doing the right thing.. oneOf is a XOR.. any values must much one and only one of the schemas in the list. the example in the pact file {number: 100} matches both of the schemas in the oneOf. (in the second schema remarks is not required, sot it can be missing)

    Even if you had {number: 100, remarks: "foo"} that would match both schemas, since the first schema accepts any additional properties.

    most values will never match your oneOf since they are not mutually exclusive. (Actually the following will match {number: 100: remarks: ["not", "a", "string"]} but I guess that wasn’t your intention)

    You can either fix the schemas in the list, or maybe what you are really after is an anyOf.. which to be honest you don’t even need, having the second schema is enough.

    Check the json schema specification for details

  2. Pablo Cabo Account Deactivated reporter

    That’s interesting, It would be great to clarify this en the documentation, because generates confusion. I would use anyOf. Thanks!

  3. Log in to comment