open-api icon indicating copy to clipboard operation
open-api copied to clipboard

validateResponse fails even though response is valid

Open JasonSome opened this issue 5 years ago • 6 comments

I am using the vanilla validateAllResponses from express-openapi and it seems to always throw an error on me when using a response that returns a JSON. Why could that be happening?

Example YAML (OpenAPI 3.0) for that specific path:

/HealthCheck:
    get:
      operationId: healthCheck
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                      - OK

validateAllResponses on response "{"status": "OK"}" (without the outer quotation marks, of course) says: "Invalid response for status code 200: should be object". What am I missing? Isn't this a valid object?

JasonSome avatar Feb 25 '20 10:02 JasonSome

that looks correct to me. i'd look at examples in the sample project testing suites.

jsdevel avatar Feb 25 '20 21:02 jsdevel

There is no example that uses this specific validateAllResponses from the website, and it seems like it doesn't work, since my responses are indeed correct.

JasonSome avatar Feb 29 '20 22:02 JasonSome

Did you manage to resolve this? I am experiencing the same type of issue as you. Validation seems to be failing for arrays that are clearly arrays and objects that are clearly objects. It seems like the validateResponse-function is not working properly.

Edit: I am using promiseMode = true and args.operations, could that play a part in this?

akselikap avatar May 11 '20 08:05 akselikap

I also had this problem. I think the problem is that the example of validateAllResponses has a bug in it (at least for JSON responses).

If you add a console.log() in it, you'll see that it's called twice. First time for the JSON, second time when the JSON has been stringified, ready to be sent on. At this point it is a string, so fails the validation.

const validateAllResponses = (req, res, next) => {
  const strictValidation = req.apiDoc['x-express-openapi-validation-strict']
    ? true
    : false;
  if (typeof res.validateResponse === 'function') {
    const send = res.send;
    res.send = function expressOpenAPISend(...args) {
      const onlyWarn = !strictValidation;
      if (res.get('x-express-openapi-validation-error-for') !== undefined) {
        return send.apply(res, args);
      }
      const body = args[0];
      let validation = res.validateResponse(res.statusCode, body);
      let validationMessage;
      if (validation === undefined) {
        validation = { message: undefined, errors: undefined };
      }
      if (validation.errors) {
        const errorList = Array.from(validation.errors)
          .map(_ => _.message)
          .join(',');
        validationMessage = `Invalid response for status code ${res.statusCode}: ${errorList}`;
        console.warn(validationMessage);
        // Set to avoid a loop, and to provide the original status code
        res.set(
          'x-express-openapi-validation-error-for',
          res.statusCode.toString(),
        );
      }
      if (onlyWarn || !validation.errors) {
        // Restore send. If this is a JSON response it will be converted into a
        // string and resent through `res.send`.
        res.send = send;
        return send.apply(res, args);
      } else {
        res.status(500);
        return res.json({ error: validationMessage });
      }
    };
  }
  next();
};

The main difference being restoring res.send after the first pass.

31,33d30
<         // Restore send. If this is a JSON response it will be converted into a
<         // string and resent through `res.send`.
<         res.send = send;

I don't think this is the main fix however so not put it forward. I don't think this works if you just do res.json({}) but I use res.send({}) exclusively so works fine until I can do some proper digging.

kwakwaversal avatar May 12 '20 09:05 kwakwaversal

@JasonSome This was asked quite some time ago. But also had this problem and it is because it overwrites the res.send but not the res.json. So if you would change all the send to json it will work for all json responses but probably not any send responses. I changed mine to json, because I just respond with json.

malnor avatar Dec 18 '20 14:12 malnor

In case someone stumbles on this later on, here is the working version for Json responses:

 function validateAllResponses(req: any, res: any, next: any) {
    const strictValidation = req.apiDoc['x-express-openapi-validation-strict']
      ? true
      : false;
    if (typeof res.validateResponse === 'function') {
      const send = res.json;
      res.json = function expressOpenAPISend(...args: any[]) {
        const onlyWarn = !strictValidation;
        if (res.get('x-express-openapi-validation-error-for') !== undefined) {
          return send.apply(res, args);
        }
        const body = args[0];
        let validation = res.validateResponse(res.statusCode, body);
        let validationMessage;
        if (validation === undefined) {
          validation = { message: undefined, errors: undefined };
        }
        if (validation.errors) {
          const errorList = Array.from(validation.errors)
            .map((_) => _.message)
            .join(',');
          validationMessage = `Invalid response for status code ${res.statusCode}: ${errorList}`;
          // Set to avoid a loop, and to provide the original status code
          res.set(
            'x-express-openapi-validation-error-for',
            res.statusCode.toString()
          );
        }
        if (onlyWarn || !validation.errors) {
          return send.apply(res, args);
        } else {
          res.status(500);
          return res.json({ error: validationMessage });
        }
      };
    }
    next();
  }

aperkaz avatar Mar 30 '21 07:03 aperkaz