Gi60s / openapi-enforcer

Apache License 2.0
94 stars 23 forks source link

Validation of optional response properties fails for undefined values #161

Closed baranga closed 7 months ago

baranga commented 7 months ago

I have a response schema with optional properties (not listed as required). In my response mapping I do a lot of this:

function mapResponse(data) {
  return {
    foo: data.foo ? mapFoo(data.foo) : undefined,
  };
}

When I return the response as JS object from my controller the response validation will throw an error like this: Expected a string. Received: undefined. When I manually serialize my reponse (JSON.stringify(...)) the validation does not complain.

I know the errors are technically correct but that's pretty inconvenient. My framework usually does the conversion to JSON after response validation and I don't want to serialize all response manually nor walk deep object trees and strip properties with undefined as value.

Gi60s commented 7 months ago

Hello, @baranga. Thanks for reaching out.

The enforcer needs to perform validations on deserialized values.

I think we're on the same page here for what that means, but to be sure, here is an example. A JavaScript Date object is deserialized and it normally serializes to an ISO datetime like this: 2024-02-28T08:00:00.000Z.

A second step for the enforcer is serializing and deserializing data. Based on your OpenAPI schema the serialization may differ. Here are two examples of schemas that deserialize the same value differently.

For the value 48656c6c6f20426172616e6761, if the schema is { type: string } then its deserialized value is also a string: 48656c6c6f20426172616e6761. On the other hand, if the schema is { type: string, format: byte } then it will deserialize to a Buffer object with length of 13.

The openapi-enforcer handles validation on deserialized values. It also handles serializing and deserializing. It also has a way to create custom serializations, deserializations, and validations for custom formats. (Example package, for bigint: https://www.npmjs.com/package/openapi-enforcer-bigint)

If your interested in an Express middleware that handles serialization, deserialization, and validation for you, please check out the documentation for the OpenAPI Enforcer Middleware.

Let me know if that answered your question. If I misunderstood something or if you have additional questions please let me know.

baranga commented 7 months ago

@Gi60s Sorry, seems like I didn't get my point across :sweat_smile:

I've to use a koa stack (strapi headless cms) and implemented the validation middleware myself. The response body is passed back by the controller as plain JS object. When I take that object (including the properties with undefined values) and pass it to the response validation I get the mentioned error. If I manually serialize that to JSON the problematic properties will be stripped and the validation passes.

I think that's the proper default behavior but it's quite inconvenient. Serializing the response just for validation before it gets serialized again by koa feels to expensive for me. So maybe there is some kind of option to ignore the properties with undefined values? They'll get stripped later on anyway.

Gi60s commented 7 months ago

Ok, I believe I understand what you're saying now. Thanks for clarifying.

There is not currently a way to skip validating properties that contain and undefined value, but it may not be too hard to implement. I'll try taking a look at it today and I'll let you know what I find.

I agree that serializing twice for no good reason is expensive.

Gi60s commented 7 months ago

@baranga I've implemented a change to ignore undefined property values via a configuration option or via an option in the schema.validate function.

Here are the two ways to allow undefined property values.

Ignore Undefined Property Values Globally For the OpenAPI Enforcer

const Enforcer = require('openapi-enforcer')

Enforcer.config.ignoreUndefinedPropertyValues = true

Ignore Undefined Property Values for One Validation

const schema = Enforcer.v3_0.Schema({
    type: 'object',
    properties: {
        str: { type: 'string' }
    }
})

schema.validate({ str: undefined}, { ignoreUndefinedPropertyValues: true })

This has been tested and deployed to version 1.23.0. It has not yet been documented on the website but hopefully will be in the next day or so.

Please check it out and let me know if this works for you and please continue to submit issues as you have them.

Thanks for your help.