Discriminator Support in Specmatic

Note: For best support of this feature please use Specmatic version 2.0.41 onwards.

Overview

OpenAPI’s discriminator feature allows APIs to handle polymorphic schemas effectively by specifying how to differentiate between different subtypes of a base schema. Specmatic provides comprehensive support for discriminators across its core functionalities: stubbing, testing, example generation, and backward compatibility checks.

For a detailed understanding of OpenAPI discriminator, refer to the official OpenAPI documentation.

Example Implementation

Here’s a visual representation of specification example that we will follow through this documentation. We can observe how discriminator works here:

classDiagram
    class Pet {
        +String name
        +Integer age
        +String petType
    }
    class Dog {
        +String name
        +Integer age
        +String petType="dog"
        +Integer barkVolume
    }
    class Cat {
        +String name
        +Integer age
        +String petType="cat"
        +Integer whiskerLength
    }
    Pet <|-- Dog : petType="dog"
    Pet <|-- Cat : petType="cat"

The petType field acts as the discriminator, determining whether an instance is a Dog or Cat, each with their unique properties while sharing the base Pet properties. Following is the complete specification that we will follow in this documentation to better understand discriminator support in Specmatic:

openapi: 3.0.0
info:
  title: Pet Store API
  version: 1.0.0
paths:
  /pets:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
      responses:
        '200':
          description: The response for Pets post operation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'

components:
  schemas:
    Pet:
      required:
        - petType
        - name
        - age
      properties:
        petType:
          type: string
        name:
          type: string
          description: The name of the pet
        age:
          type: integer
          minimum: 0
          description: Age of the pet in years
      discriminator:
        propertyName: petType
      oneOf:
        - $ref: '#/components/schemas/Cat'
        - $ref: '#/components/schemas/Dog'

    Cat:
      allOf:
        - $ref: '#/components/schemas/Pet'
        - type: object
          required:
            - whiskerLength
          properties:
            whiskerLength:
              type: integer
              minimum: 0
              description: Length of whiskers in centimeters

    Dog:
      allOf:
        - $ref: '#/components/schemas/Pet'
        - type: object
          required:
            - barkVolume
          properties:
            barkVolume:
              type: integer
              minimum: 0
              maximum: 100
              description: Volume of bark in decibels

Save this specification in a file named petstore.yaml. Now let’s walk through how to use Specmatic’s features with this specification.

1. Service Virtualization with Discriminator Support

Let’s start the Specmatic stub server. To spin up a stub server with the petstore.yaml we authored earlier, run below command.

  • java -jar specmatic.jar stub petstore.yaml
    
  • npx specmatic stub petstore.yaml
    
  • docker run -v "/local-directory/petstore.yaml:/usr/src/app/petstore.yaml" -p 9000:9000 znsio/specmatic stub "petstore.yaml"
    

This should start your stub server on port 9000 by default as below.

Loading petstore.yaml
API Specification Summary: petstore.yaml
  OpenAPI Version: 3.0.1
  API Paths: 1, API Operations: 1


Stub server is running on http://0.0.0.0:9000. Ctrl + C to stop.

The stub server provides two key functionalities with discriminator support:

  1. Request Validation: When a request is received, Specmatic:
    • Extracts the petType discriminator value
    • Maps it to the correct schema (Dog/Cat)
    • Validates the request body against that specific schema

Example - Valid Dog Request:

curl -X POST http://localhost:9000/pets -H "Content-Type: application/json" \
-d '{
  "name": "Rex",
  "age": 3,
  "petType": "dog",
  "barkVolume": 7
}'

Example - Invalid Dog Request (missing required barkVolume):

curl -X POST http://localhost:9000/pets -H "Content-Type: application/json" \
-d '{
  "name": "Rex",
  "age": 3,
  "petType": "dog"
}'

Error Response:

In scenario "POST /pets. Response: This is a 200 response."
API: POST /pets -> 200

  >> REQUEST.BODY.barkVolume
  
     Key named barkVolume in the contract was not found in the request
  1. Response Generation: For valid requests, Specmatic:
    • Identifies the correct response schema based on the request’s discriminator value
    • Generates a response matching that schema
    • Maintains consistency between request and response types

Example - Cat Request and Auto-generated Response:

curl -X POST http://localhost:9000/pets -H "Content-Type: application/json" \
-d '{
  "name": "Whiskers",
  "age": 5,
  "petType": "cat",
  "whiskerLength": 10
}'

Response:

{
  "name": "Whiskers",
  "age": 5,
  "petType": "cat",
  "whiskerLength": 10
}

2. Contract Testing with Discriminator Awareness

Now, let’s run Specmatic’s test. To start the test with the petstore.yaml we authored earlier, run below command.

  • java -jar specmatic.jar test petstore.yaml --testBaseURL=http://localhost:9000
    
  • npx specmatic test petstore.yaml --testBaseURL=http://localhost:9000
    
  • docker run -v "./petstore.yaml:/usr/src/app/petstore.yaml" znsio/specmatic test petstore.yaml --port=9000 --host=host.docker.internal
    

Specmatic’s contract testing with discriminators works in two directions:

  1. Request Generation:
    • Creates test requests for each subtype (Dog/Cat)
    • Includes required discriminator property (petType)
    • Adds subtype-specific properties (barkVolume for Dog, whiskerLength for Cat)
    • Tests boundary conditions (missing/invalid discriminator values)
  2. Response Validation:
    • Verifies response matches the schema corresponding to request’s discriminator value
    • Ensures presence of discriminator property in response
    • Validates subtype-specific properties
    • Checks consistency between request and response types

Here are few example requests :

a. Testing Valid Dog Request:

   POST /pets
   Content-Type: application/json
   {
     "name": "Rex",
     "age": 3,
     "petType": "dog",
     "barkVolume": 7
   }

b. Testing Valid Cat Request:

   POST /pets
   Content-Type: application/json
   {
     "name": "Whiskers",
     "age": 5,
     "petType": "cat",
     "whiskerLength": 10
   }

c. Testing Invalid Discriminator Value:

   POST /pets
   Content-Type: application/json
   {
     "name": "Invalid",
     "age": 3,
     "petType": "bird",
     "wingspan": 20
   }

3. Example Generation

Specmatic provides an interactive example generation interface. Let’s start it with:

specmatic examples interactive --contract-file=petstore.yaml

This launches an interactive server and provides a URL http://localhost:9001/_specmatic/examples. Opening this in your browser shows:

Screenshot of Interactive Example Generator Interface

The interface allows you to:

  1. Select different operations
  2. Generate examples for specific subtypes
  3. View example requests and responses

For our Pet Store API, press the Generate button, to generates examples for each discriminator type. Following is how the interface would look once the examples are generated:

Screenshot of Interactive Example Generator Interface

Please find below, the two examples that got generated

Dog Example:

{
  "http-request": {
    "method": "POST",
    "path": "/pets",
    "headers": {
      "Content-Type": "application/json"
    },
    "body": {
      "name": "Rex",
      "age": 3,
      "petType": "dog",
      "barkVolume": 7
    }
  },
  "http-response": {
    "status": 200,
    "status-text": "OK",
    "headers": {
        "Content-Type": "application/json"
    },
    "body": {
      "name": "Rex",
      "age": 3,
      "petType": "dog",
      "barkVolume": 7
    }
  }
}

Cat Example:

{
  "http-request": {
    "method": "POST",
    "path": "/pets",
    "headers": {
      "Content-Type": "application/json"
    },
    "body": {
      "name": "Whiskers",
      "age": 5,
      "petType": "cat",
      "whiskerLength": 10
    }
  },
  "http-response": {
    "status": 200,
    "status-text": "OK",
    "headers": {
        "Content-Type": "application/json"
    },
    "body": {
      "name": "Whiskers",
      "age": 5,
      "petType": "cat",
      "whiskerLength": 10
    }
  }
}

4. Backward Compatibility

Specmatic also check backward compatibility of discriminator based OpenAPI Specifications. To check backward compatibility you can run the following command:

specmatic backward-compatibility-check 

Note: For complete understanding of backward compatibility please refer Backward Compatibility User Guide

Specmatic checks for breaking changes in:

  • Discriminator property names
  • Mapping configurations
  • Subtype schemas
  • Required properties

Breaking changes would include:

  • Removing or renaming the petType discriminator
  • Changing mapping values
  • Adding required properties to subtypes
  • Modifying existing subtype properties

Best Practices

  1. Always specify explicit mappings in the discriminator object
  2. Maintain consistent property names across subtypes
  3. Use meaningful discriminator values
  4. Include examples for each subtype

References

  1. OpenAPI Discriminator Documentation