Hi Team! I'm having trouble getting my OpenAPI spe...
# pactflow
d
Hi Team! I'm having trouble getting my OpenAPI specification to be parsed by Pactflow using discriminators. I have followed the guidelines outlined here: Keyword Support | PactFlow Documentation The error I am seeing is:
"message": "discriminator: oneOf subschemas (or referenced schemas) must have \"properties/transactionType\""
What I cannot understand is why it's complaining about the transactionType not being declared within subschemas, when all of them explicityly define it alongside a fixed enum value. Here is a sample of what my schema definitions look like:
Copy code
Transaction:
      required:
        - transactionDetails
        - transactionType
      type: object
      properties:
        transactionType:
          type: string
          description: 'Type of transaction. Used as a discriminator for polymorphic deserialization.'
          example: transaction_a
        transactionDetails:
          $ref: '#/components/schemas/TransactionDetails'
      description: Transaction Details
      discriminator:
        propertyName: transactionType
      oneOf:
        - $ref: '#/components/schemas/TransactionA'
        - $ref: '#/components/schemas/TransactionB'
TransactionA:
      required:
        - transactionDetails
        - transactionType
      type: object
      description: Transaction A Details
      allOf:
        - $ref: '#/components/schemas/Transaction'
        - type: object
          properties:
            transactionDetails:
              $ref: '#/components/schemas/TransactionADetails'
            transactionType:
              type: string
              description: Constant discriminator identifying this transaction subtype
              readOnly: true
              example: transaction_a
              enum:
                - transaction_a        
TransactionB:
      required:
        - transactionDetails
        - transactionType
      type: object
      description: Transaction B Details
      allOf:
        - $ref: '#/components/schemas/Transaction'
        - type: object
          properties:
            transactionDetails:
              $ref: '#/components/schemas/TransactionBDetails'
            transactionType:
              type: string
              description: Constant discriminator identifying this transaction subtype
              readOnly: true
              example: transaction_b
              enum:
                - transaction_b
m
Interesting. I think we can update that advice (we now support implicit mapping also). But that aside
Is that a valid schema though? It looks to be a recursive definition. i.e. a
Transaction
is
oneOf
TransactionA
or
TransactionB
, which is a
Transaction
(which is one of
TransactionA
,
TransactionB
…)
Normally you would arrange it hierarchically, like this:
Copy code
openapi: 3.1.0
info:
  title: Pet Polymorphism Example
  version: '1.0'
components:
  schemas:
    Animal:
      type: object
      required:
        - pet_type
      discriminator:
        propertyName: pet_type
        mapping:
          dog: '#/components/schemas/Dog'
          cat: '#/components/schemas/Cat'
      oneOf:
        - $ref: '#/components/schemas/Dog'
        - $ref: '#/components/schemas/Cat'

    Dog:
      type: object
      required:
        - pet_type
      properties:
        pet_type:
          type: string
          const: dog
        barkVolume:
          type: integer

    Cat:
      type: object
      required:
        - pet_type
      properties:
        pet_type:
          type: string
          const: cat
        whiskerLength:
          type: number
d
I agree there does seem to be some recursion going on. We're using springdoc to auto generate the specification from our Spring API's. Where
Transaction
is an abstract parent class and `TransactionA`/`TransactionB` inherit from it.
allOf
within the subschemas is be being used to inherit all properties from
Transaction
closer to the
allOf
example here.
m
Yeah, I suspect what’s going on, is that it’s OK for the child to inherit or reference the parent, but having the parent then reference the child is what’s making the JSON schema parser (ajv) get confused
d
Let me investigate further around the parent child mapping that's being generated. Thanks!
What do you think about this thinking in the same context as Pets above. In the parent Animal schema we need a
tail
property which we want to be inherited by all subschemas. In order to do so we have to utilise
allOf
within each subschema to reflect the polymorphic nature of the code structure. With using a discriminator on
Animal
, we then have to use a
oneOf
property too. This results in the recursive nature and is how our OpenAPI specification are being generated by
spingdoc
. What are your recommendations for getting around this?
Copy code
openapi: 3.1.0
info:
  title: Pet Polymorphism Example
  version: 1.0.0-oas3.1
components:
  schemas:
    Animal:
      type: object
      properties:
        tail:
          type: string
          examples:
            - huge
      required:
        - pet_type
      discriminator:
        propertyName: pet_type
        mapping:
          dog: '#/components/schemas/Dog'
          cat: '#/components/schemas/Cat'
      oneOf:
        - $ref: '#/components/schemas/Dog'
        - $ref: '#/components/schemas/Cat'
    Dog:
      type: object
      required:
        - pet_type
      properties:
        pet_type:
          type: string
          const: dog
        barkVolume:
          type: integer
    Cat:
      type: object
      required:
        - pet_type
      allOf:
          - $ref: '#/components/schemas/Animal'
          - type: object
            properties:
              pet_type:
                type: string
                const: cat
              whiskerLength:
                type: number
@Matt (pactflow.io / pact-js / pact-go) I have been doing some further reasearch. When the OpenAPI specification is generated for
TransactionA
using
springdoc-openapi-ui
, which extends from the main
Transaction
object, we get the following YAML schema.
Transaction
Copy code
Transaction:
  required:
    - transactionDetails
    - transactionType
  type: object
  properties:
    transactionType:
      type: string
      description: 'Type of transaction. Used as a discriminator for polymorphic deserialization. Valid exam
      example: loan_renewal
    transactionDetails:
      $ref: '#/components/schemas/TransactionDetails'
  description: Base Transaction type. The actual type is determined by the 'transactionType' property.
  discriminator:
    propertyName: transactionType
    mapping:
      loan: '#/components/schemas/TransactionA'
  oneOf:
    - $ref: '#/components/schemas/TransactionA'
TransactionA
Copy code
TransactionA:
  required:
    - transactionDetails
    - transactionType
  type: object
  description: Loan Transaction Details
  allOf:
	#With or without the $ref property we get the error. 
    - $ref: '#/components/schemas/Transaction'
    - type: object
      properties:
        transactionDetails:
          $ref: '#/components/schemas/TransactionADetails'
        transactionType:
          type: string
          description: Constant discriminator identifying this transaction subtype
          readOnly: true
          example: loan
          enum:
            - loan
This causes discriminator mapping issues within Pactflow schema validation due to the
allOf
which gets generated for inheritance. To get this to be valid I can manually edit the schema to be like so:
Copy code
TransactionA:
  required:
    - transactionDetails
    - transactionType
  type: object
  description: Loan Transaction Details
  properties:
    transactionDetails:
      $ref: '#/components/schemas/TransactionADetails'
    transactionType:
      type: string
      description: Constant discriminator identifying this transaction subtype
      readOnly: true
      example: loan
      enum:
        - loan
So is this expected behaviour of Pactflow? Even without the #ref back up to the parent to inherit properties, having properties inside of an
allOf
seems to break the discriminator mapping?
m
Yeah I’m not sure to be honest. A quick way to check, is by converting this into a schema and loading it into
ajv
directly. If
ajv
doesn’t support it, we’d need to do something additional in PactFlow’s tooling to support it (e.g. a workaround, effectively)
As an FYI, the tool we created to compare the OAD against the pact is this: https://github.com/pactflow/openapi-pact-comparator/