Encountered "Converting circular structure to JSON error" when returning the customized BoomError response through server onPreResponse
YiHaoLin1207 opened this issue · comments
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.
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.
First triggering, data was null
Second triggering, data was not null but we started getting 500 internal server error
Third triggering, data was not null but seems with circular structure (I guess that's why we got "Converting circular structure to JSON error")
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?