Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encountered "Converting circular structure to JSON error" when returning the customized BoomError response through server onPreResponse #4440

Open
YiHaoLin1207 opened this issue Mar 22, 2023 · 0 comments
Labels
support Questions, discussions, and general support

Comments

@YiHaoLin1207
Copy link

Support plan

  • is this issue currently blocking your project? (yes/no): no
  • is this issue affecting a production system? (yes/no): yes

Context

  • node version: 18.14.2
  • module version:
    • @hapi/hapi: 21.1.0
    • joi: 17.8.3
  • environment (e.g. node, browser, native): node
  • used with (e.g. hapi application, another framework, standalone, ...): hapi application
  • any other relevant information:

How can we help?

Problem Description
This error happened when we were using joi for schema validation through the validation function provided by hapi.

When we used the following way to throw an error when validation failed, it threw 500 internal server error when clients gave null value of refId twice or above (first time didn't have the 500 internal server error but 400 bad request which was expected).

const schema = Joi.object({
  refId: refId.required().error(new Error('refId is required field.')),
});

Investigation

This is our error plugin which handles Boom error

index.js (error plugin)

const isTrue = require('../../modules/conditional-utils/isTrue');

const BoomError = require('./BoomError');

const register = async server => {
  const { logger } = server.plugins;

  server.ext('onPreResponse', (request, h) => {
    const { response } = request;

    // ...
    // ...skip not important parts

    if (isTrue(response.isBoom)) {
      const error = new BoomError(response);
      const { body, code, message } = error.render();

      return h
        .response(body)
        .code(code)
        .message(message);
    }

    return h.continue;
  });
};

// ...

BoomError.js
BoomError model gets the 'data' from the response and then put it for BaseError

// Boom: https://github.com/hapijs/boom
// HTTP-friendly error objects

const get = require('lodash/get');

const BaseError = require('./BaseError');

const defaultCode = 'BOOM_ERROR';

/**
 * BoomError
 *
 * @class
 * @extends BaseError
 */
module.exports = class BoomError extends BaseError {
  /**
   * Create an instance of BoomError
   *
   * @param {Object} object - the Boom object instance
   * @memberof BoomError
   */
  constructor(object) {
    const message = get(object, ['output', 'payload', 'message']);
    const name = get(object, ['output', 'payload', 'error']);
    const status = get(object, ['output', 'payload', 'statusCode']);
    const data = get(object, ['data']);

    // Create the error using our Base Error
    super(message, defaultCode, name, status, data);
  }
};

BaseError.js

BaseError returns the data as a part of body

// ...
// ...

module.exports = class BaseError extends ExtendableError {
  /**
   * Create an instance of BaseError
   *
   * @param {string} message - the error message
   * @param {string} code - the error code
   * @param {string} name - the error name
   * @param {number} status - the http status code
   * @param {*} [data] - the error object and/or additional data
   * @memberof BaseError
   */
  constructor(message, code, name, status, data) {
    throwIfNotString(message);
    throwIfNotString(code);
    throwIfNotString(name);
    throwIfNotNumber(status);

    super(message);
    this.code = code;
    this.name = name;
    this.status = status;
    this.data = data;

    // This field is used to allow internal services to determine
    // whether the error object is an instance of Base Error (a bit like 'isBoom')
    this.isBaseError = true;
  }

  // Render the error message for response to the user
  render() {
    const { status, name, code, message } = this;
    const error = { status, name, code, message };
    const data = isPresent(this.data) ? this.data : null;

    return {
      body: { error, data },
      code: status,
      message: name,
    };
  }
};

After doing some tracking, the error "Converting circular structure to JSON error" happened when executing await internals.marshal(response); in the transmit.js of hapi/lib as shown below.
image

Finally, I found it seems it's related to #4229 failAction: detailedError to log, defaultError to response of the release 21.0.0

In the validation.js file, data field of defaultError will be different between the first and second triggering of joi validation.

image

First triggering, data was null
image
image

Second triggering, data was not null but we started getting 500 internal server error
image
image

Third triggering, data was not null but seems with circular structure (I guess that's why we got "Converting circular structure to JSON error")
image
image

I would like to know why this happened and if we can fix this issue on our side, or hapi will also deal with this issue?

@YiHaoLin1207 YiHaoLin1207 added the support Questions, discussions, and general support label Mar 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
support Questions, discussions, and general support
Projects
None yet
Development

No branches or pull requests

1 participant