Gi60s / openapi-enforcer

Apache License 2.0
94 stars 22 forks source link

Dates in request string properties are failing with a false positive #134

Closed AndrewChurchill closed 2 years ago

AndrewChurchill commented 2 years ago

Requests with string type properties with format "date-time" are failing with the following error:

Expected a valid date object. Received: "2022-03-28T21:02:21.427Z"

As you can see, this is an acceptable date string, so validation should not be failing.

It also fails with the string "2022-03-28".

An example of the schema is given below:

properties:
  date:
    type: string
    format: date-time

Alternatively, is there a way to ignore date-time string formatting validations? Thank you for your help!

AndrewChurchill commented 2 years ago

When I pass the date string into this method, it comes back false. I'm guessing something isn't getting converted when it should (maybe more date formats need to be recognized).

https://github.com/Gi60s/openapi-enforcer/blob/a0874bf27a8655bd0727908664a37058659ffc6c/src/util.js#L289

Also, I'd be fine with turning off date format validations if there's a way to configure that?

Gi60s commented 2 years ago

Hey Andrew. Thanks for the question.

I'd have to see what your code is doing to know for sure, but I think what is going on here is that you're validating a value against a schema using the Schema#validate function. If that is the case then it expects a deserialized value as input. The date-time string 2022-03-28T21:02:21.427Z is a valid serialized value, but the validate function required deserialized values. See the documentation for the validate function: https://openapi-enforcer.com/api/components/schema.html#validate

If you'd like you can deserialize your value prior to validating (https://openapi-enforcer.com/api/components/schema.html#deserialize).

Let me know if you have any other questions or if this has resolved your question.

AndrewChurchill commented 2 years ago

Hi, thank you for the quick response! Here's a bit more detail of what I'm doing:

const [openapi, error, warning] = await Enforcer('./path/to/openapi.yaml', {
    fullResult: true,
});

// Parsed from HTTP request
const request = {
    accept: 'application/json',
    method: 'post',
    path: '/path',
    contentType: 'application/json',
    body: {
        createdAtUtc: '1970-01-01T00:00:00.000Z',
        updatedAtUtc: '1970-01-01T00:00:00.000Z',
        locale: 'en-US',
        // Other properties...
    },
};

// Parsed from HTTP response
const response = {
    headers: {
        contentType: 'application/json',
        statusCode: 200,
    },
    body: {
        id: 'a-guid',
        createdBy: 'Someone',
        createdAtUtc: '2022-03-28T21:02:21.427Z',
        updatedAtUtc: '1970-01-01T00:00:00.000Z',
        locale: 'en-US',
        status: 'New',
        // Other properties...
    },
};

// Throw after we call this if error is not undefined.
// This is where I'm seeing the formatting error.
const [req, error] = openapi.request(request);

// This isn't reached yet, but I'm guessing it would throw the same formatting error.
const { headers, body } = response;
const [res, error] = request.response(headers.statusCode, body, headers);

Based on what you're saying, do I need to go through and call Schema#deserialize on the dates if I encounter them? For example, use regex to determine if it's a date string and then deserialize if it is?

Thanks again for your help!

Gi60s commented 2 years ago

The docs specify that the body should already be deserialized. All other input types should remain serialized. (https://openapi-enforcer.com/api/components/openapi.html#request)

In your case, if you have access to the schema, it will do the deserializing for you. For example:

const [openapi, error, warning] = await Enforcer('./path/to/openapi.yaml', {
    fullResult: true,
});

const schema = openapi.paths['/path'].post.requestBody.content['application/json'].schema
const [body] = schema.deserialize({
    createdAtUtc: '1970-01-01T00:00:00.000Z',
    updatedAtUtc: '1970-01-01T00:00:00.000Z',
    locale: 'en-US',
    // Other properties...
})

// Parsed from HTTP request
const request = {
    accept: 'application/json',
    method: 'post',
    path: '/path',
    contentType: 'application/json',
    body,
};

...

I haven't tested the above example, but it should be close to correct.

Also, I'm not sure if this would be of interest to you, but if you're running an express server then there is a middleware you can use that will do this for you: https://middleware.openapi-enforcer.com/guide/

There is also a library for AWS lambdas: https://www.npmjs.com/package/openapi-enforcer-lambda

AndrewChurchill commented 2 years ago

Thanks for the great info! We're not using an Express server, but looks like a good option if we ever use that in the future.

I was able to fix this by changing the way we parse the body of the request.

parsedBody = JSON.parse(body, (_, value) => {
    if (typeof value === 'string') {
        if (isDateString(value)) {
            return new Date(value);
        } else {
            return value;
        }
    } else {
        return value;
    }
});

Thanks again for your help! I have been looking around for a module that can validate requests/responses against an OAS for a while, and this is definitely the best one I found.

Gi60s commented 2 years ago

I'm glad you've found a solution that works for you, although you may want to eventually move to using the schema deserialize method as it will handle more cases.

I'm glad you like the library. Have a great day.