hapijs / hapijs/hapi

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

Open
#4,440 0 comments 0 reactions 0 assignees View on GitHub
support
Dominant language
JavaScript
Stars
14.8k
Forks
1.4k
Avg merge
22d 3h
Merged PRs (30d)
1

Description

#### 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).
```js
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)
```js
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`

```js
// 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`

```js
// ...
// ...

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](https://user-images.githubusercontent.com/39984487/226816448-6bad5a11-bff3-4dc9-9e6c-fb6aa7212543.png)

Finally, I found it seems it's related to [#4229 failAction: detailedError to log, defaultError to response](https://github.com/hapijs/hapi/issues/4229) 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](https://user-images.githubusercontent.com/39984487/226819709-d14af29c-e1ed-474d-a6b3-d853e020d806.png)

First triggering, data was null
![image](https://user-images.githubusercontent.com/39984487/226820437-dc93039e-41a7-4472-b358-d06b8e659d1c.png)
image

Second triggering, data was not null but we started getting 500 internal server error
![image](https://user-images.githubusercontent.com/39984487/226821364-35c68d61-e5da-4f6d-b0e1-45dcb2501d9e.png)
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](https://user-images.githubusercontent.com/39984487/226821605-d30d7493-bcbe-4aa7-8704-8c0376abdf53.png)
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?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.