Skip to content

Errors in polymorphic objects not correctly reported if objects are in an array #669

@jrosmithMG

Description

@jrosmithMG

Describe the bug
When a malformed polymorphic implementation is in an array, errors returned by express-open-api validator state that the object must implement all required properties of all polymorphic implementations despite the discriminating property being present.

Versions:
express-openapi-validator v4.13.2
express v4.17.1
Node.js v14.7.4
npm v6.14.4

To Reproduce
See examples - spec, express setup, and curl requests are all provided

Actual behavior
Errors do not take discriminator into account and return errors relating to all polymorphic object implementations

[
  {
    "path": ".body[0].poly1_prop",
    "message": "should have required property 'poly1_prop'",
    "errorCode": "required.openapi.validation"
  },
  {
    "path": ".body[0].poly2_prop",
    "message": "should have required property 'poly2_prop'",
    "errorCode": "required.openapi.validation"
  },
  {
    "path": ".body[0]",
    "message": "should match exactly one schema in oneOf",
    "errorCode": "oneOf.openapi.validation"
  },
  {
    "path": ".body[1].poly1_prop",
    "message": "should have required property 'poly1_prop'",
    "errorCode": "required.openapi.validation"
  },
  {
    "path": ".body[1].poly2_prop",
    "message": "should have required property 'poly2_prop'",
    "errorCode": "required.openapi.validation"
  },
  {
    "path": ".body[1]",
    "message": "should match exactly one schema in oneOf",
    "errorCode": "oneOf.openapi.validation"
  }
]

Expected behavior
When the two malformed polymorphic objects are in an array and have valid discriminators, the following errors should be returned:

[
  {
    "path": ".body[0].poly1_prop",
    "message": "should have required property 'poly1_prop'",
    "errorCode": "required.openapi.validation"
  },
  {
    "path": ".body[1].poly2_prop",
    "message": "should have required property 'poly2_prop'",
    "errorCode": "required.openapi.validation"
  }
]

Examples and context
Example spec:

info:
  title: Example API
  version: 0.1.0
servers:
  - url: https://localhost:3030/
paths:
  /single:
    post:
      description: Request
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PolyObject'
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                type: object
                $ref:  "#/components/schemas/PolyObject"
  /array:
    post:
      description: Request
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/PolyObject'
      responses:
        "200":
          description: Response
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref:  "#/components/schemas/PolyObject"
components:
  schemas:
    PolyObject:
      type: object
      discriminator:
        propertyName: object_type
        mapping:
          PolyObject1: '#/components/schemas/PolyObject1'
          PolyObject2: '#/components/schemas/PolyObject2'
      oneOf:
        - $ref: '#/components/schemas/PolyObject1'
        - $ref: '#/components/schemas/PolyObject2'

    PolyObjectBase:
      type: object
      required:
        - object_type
        - shared_prop
      properties:
        object_type:
          type: string
          enum:
            - PolyObject1
            - PolyObject2
        shared_prop:
          type: string
    
    PolyObject1:
      allOf:
        - $ref: '#/components/schemas/PolyObjectBase'
        - required:
          - poly1_prop
        - type: object
          properties:
            poly1_prop:
              type: string

    PolyObject2:
      allOf:
        - $ref: '#/components/schemas/PolyObjectBase'
        - required:
          - poly2_prop
        - type: object
          properties:
           poly2_prop:
             type: string

Example express server:

const OpenApiValidator = require('express-openapi-validator');
const express = require('express');
const app = express()

app.use(express.json())

app.use(
    OpenApiValidator.middleware({
        apiSpec: './spec.yaml',
        validateResponses: true 
    })
)

app.post("/single", (req, res) => {
    res.status(200).json(req.body)
})

app.post("/array", (req, res) => {
    res.status(200).json(req.body)
})


app.use((e, req, res, next) => {
    res.status(500).json(e.errors || { error: e.message })
})
app.listen(3030, () => { console.log("Sever started") })

Example curl requests:

#!/bin/bash

# PolyObject1 missing poly1_prop 
# Expected error returned
curl -s --request POST \
    --url http://localhost:3030/single \
    --header 'content-type: application/json' \
    --data-raw '{
            "object_type": "PolyObject1",
            "shared_prop": "shared_value"
        }'

# PolyObject2 missing poly2_prop 
# Expected error returned
curl -s --request POST \
    --url http://localhost:3030/single \
    --header 'content-type: application/json' \
    --data-raw '{
            "object_type": "PolyObject2",
            "shared_prop": "shared_value"
        }'


# PolyObject1 missing poly1_prop
# and
# PolyObject2 missing poly2_prop
# Unexpected errors returned
curl -s --request POST \
    --url http://localhost:3030/array \
    --header 'content-type: application/json' \
    --data-raw '[
            {
                "object_type": "PolyObject1",
                "shared_prop": "shared_value"
            },
            {
                "object_type": "PolyObject2",
                "shared_prop": "shared_value"
            }   
        ]'

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions