express-openapi-validator icon indicating copy to clipboard operation
express-openapi-validator copied to clipboard

Nullable enum ?

Open ctiaffay-conserto opened this issue 6 years ago • 8 comments

Hi there

I have to filter a list of entities on a field which is based on a nullable enum : http://localhost/list?limit=10&filter=val_1

I need to be able to get :

  • all entities (without using the filter at all) : http://localhost/list?limit=10
  • only entities with an enum value (not null in database) : http://localhost/list?limit=10&filter=enum_val1
  • only entities with NO enum value (null in database) : http://localhost/list?limit=10&filter=

The part where I declare and use my enum field :

{
    paths: {
        '/list': {
            get: {
                tags: ['list'],
                summary: 'Return a list of entities',
                operationId: 'list',
                parameters: [
                    {
                        name: 'filter',
                        in: 'query',
                        schema: {
                            $ref: '#/components/schemas/filter',
                        },
                        allowEmptyValue: true,
                        required: false,
                    },
            }
        },
    },
    components: {
        schemas: {
            filter: {
                description: 'nullable enum',
                type: 'string',
                nullable: true,
                enum: ['val_1', 'val_2', 'val_3'],
            },
        }
    }
}

The third is the one (only entities with NO enum value) I can't figure out. The validation middleware keep telling me :

response.data[0].filter should be equal to one of the allowed values: , val_1, val_2, val_3

Thanks for the help

ctiaffay-conserto avatar Dec 02 '19 14:12 ctiaffay-conserto

Found a workaround to get it working :

enum: [null, '', 'val_1', 'val_2', 'val_3'],

The empty string '' value is needed for the request validator to accept an empty url query param when having filter= The null vaue is needed for the response validator to accept any null value in response.

But it kind of break the swagger-ui and documentation by adding two possibles values just for the "nullable" validation thing to be working.

ctiaffay-conserto avatar Dec 04 '19 10:12 ctiaffay-conserto

Cool. I appreciate the write-up and the example. It's super helpful. Based on your feedback, it seems their issue is an issue in the request validator's query param handler. I'm sure, with a small fix, we can get this working with null in the enum list. However, since you specified nullable: true that shouldn't be necessary. We'll look into that as well.

In any case, I'm glad you found a workaround in the near term.

cdimascio avatar Dec 05 '19 03:12 cdimascio

@ctiaffay-conserto i believe this will be (at least partially) resolved by https://github.com/cdimascio/express-openapi-validator/issues/190. once merged, you should be able to set allowEmptyValue: true which should solve the request part

keeping open as the response side must be addressed

cdimascio avatar Dec 30 '19 20:12 cdimascio

Hello all, I am trying to get the following to work correctly, but unfortunately I receive an error during response validation when the value is null.

This

type:
  type: string
  nullable: true
  enum:
    - ADHOC
    - SCHEDULE
    - ANONYMOUS
    - KIOSK

Causes This

Internal Server Error: /response/type must be equal to one of the allowed values: ADHOC, SCHEDULE, ANONYMOUS, KIOSK
    at ResponseValidator._validate (/my-repo/node_modules/express-openapi-validator/src/middlewares/openapi.response.validator.ts:201:13)
    at /my-repo/node_modules/express-openapi-validator/src/middlewares/openapi.response.validator.ts:73:23
    at ServerResponse.json_hook (/my-repo/node_modules/express-openapi-validator/src/framework/modded.express.mung.ts:39:16)
    at ServerResponse.json_hook (/my-repo/node_modules/express-mung/index.js:56:29)
Errors: [
  {
    path: '/response/type',
    message: 'must be equal to one of the allowed values: ADHOC, SCHEDULE, ANONYMOUS, KIOSK',
    errorCode: 'enum.openapi.validation'
  }
]

"Workaround"

The only thing I have tried that did not cause an error is the following, but it unfortunately also considers an empty object as valid as well...

Notes

  • I could not make it work with type: string as the nullable: true option
  • My attempts at changing to anyOf or allOf did not help the situation.
type:
  oneOf:
    - type: object
      nullable: true
      additionalProperties: false
    - type: string
      enum:
        - ADHOC
        - SCHEDULE
        - ANONYMOUS
        - KIOSK

@cdimascio do you have any suggestions for a schema that would work with the code base today and only allow the enum strings or null?

If not, what needs to be addressed in this library to resolve this?

Thanks in advance for your help and in general your contributions to this library!

Cheers, Jack

Jackman3005 avatar Sep 14 '23 03:09 Jackman3005

We're still running into this as well. However, reading through the OpenAPI specification, it seems as though this is actually expected behaviour. The null value should be included in the enum array, even if the type has a nullable: true property: https://github.com/OAI/OpenAPI-Specification/blob/main/proposals/2019-10-31-Clarify-Nullable.md#if-a-schema-specifies-nullable-true-and-enum-1-2-3-does-that-schema-allow-null-values-see-1900.

Therefore, the validator performs according to the OpenAPI specification and I don't think this actually a bug with the validator.

Perhaps something can be done about the generated schema for enums, but that should be addressed in nestjs/swagger instead

Tlepel avatar Nov 13 '23 10:11 Tlepel

@Tlepel So are you saying I need to add null in the enum list for my string type?

If so, that doesn't feel quite right. The list of enum values in the example I gave are all string values (I think), so I'm not sure how I can add null to the list of enums without it being interpreted as "null". null and "null" are not equal and although the API accepts null as a valid value it does not accept "null" as a valid value.

Jackman3005 avatar Nov 23 '23 07:11 Jackman3005

@Jackman3005 yes, that's right. I agree that it doesn't feel right, but according to the clarification that's how it should be specified.

However, I did some digging and found some more information for OAS 3.1, take a look at this issue: https://github.com/OAI/OpenAPI-Specification/issues/3148

Here a different solution is proposed, which could be what you're looking for:

type:
  type: ['string', 'null']
  enum:
    - ADHOC
    - SCHEDULE
    - ANONYMOUS
    - KIOSK

Tlepel avatar Nov 23 '23 07:11 Tlepel

@Tlepel I tried something similar to what you suggested as my enum was actually a $ref:

type:
  oneOf:
    - type: "null"
    - $ref: "schemas.yaml#/QuestType"

This required me to drop swagger-cli which was just archived this month in favor of redocly which I was able to get working and it also seemed to work okay with swagger-typescript-api which is another downstream consumer of this OpenAPI definition.

However, after getting that far I found out that express-openapi-validator does not yet support OAS 3.1, per this issue. The error I'm getting on startup of my API is as follows:

openapi.validator: Validating schema
openapi.validator: validation errors [
  {
    "instancePath": "/components/schemas/QuestPrototypeDetail/properties/type/oneOf/0/type",
    "schemaPath": "#/properties/type/enum",
    "keyword": "enum",
    "params": {
      "allowedValues": [
        "array",
        "boolean",
        "integer",
        "number",
        "object",
        "string"
      ]
    },
    "message": "must be equal to one of the allowed values"
  },
  {
    "instancePath": "/components/schemas/QuestPrototypeDetail/properties/type/oneOf/0",
    "schemaPath": "#/definitions/Reference/required",
    "keyword": "required",
    "params": {
      "missingProperty": "$ref"
    },
    "message": "must have required property '$ref'"
  },
  {
    "instancePath": "/components/schemas/QuestPrototypeDetail/properties/type/oneOf/0",
    "schemaPath": "#/properties/oneOf/items/oneOf",
    "keyword": "oneOf",
    "params": {
      "passingSchemas": null
    },
    "message": "must match exactly one schema in oneOf"
  },
  {
    "instancePath": "/components/schemas/QuestPrototypeDetail/properties/type",
    "schemaPath": "#/definitions/Reference/required",
    "keyword": "required",
    "params": {
      "missingProperty": "$ref"
    },
    "message": "must have required property '$ref'"
  },
  {
    "instancePath": "/components/schemas/QuestPrototypeDetail/properties/type",
    "schemaPath": "#/properties/properties/additionalProperties/oneOf",
    "keyword": "oneOf",
    "params": {
      "passingSchemas": null
    },
    "message": "must match exactly one schema in oneOf"
  },
  {
    "instancePath": "/components/schemas/QuestPrototypeDetail",
    "schemaPath": "#/definitions/Reference/required",
    "keyword": "required",
    "params": {
      "missingProperty": "$ref"
    },
    "message": "must have required property '$ref'"
  },
  {
    "instancePath": "/components/schemas/QuestPrototypeDetail",
    "schemaPath": "#/properties/schemas/patternProperties/%5E%5Ba-zA-Z0-9%5C.%5C-_%5D%2B%24/oneOf",
    "keyword": "oneOf",
    "params": {
      "passingSchemas": null
    },
    "message": "must match exactly one schema in oneOf"
  }
]

Despite the rather verbose output, I'm guessing the first error is the real error, in that null is not a supported type as of yet.

Unfortunately, this still seems to be hung up on AJV support for 3.1 which from what I can tell is only blocked by this one issue which has a $500 bounty on it if anyone is interested to tackle that...

Jackman3005 avatar Nov 23 '23 08:11 Jackman3005