diff --git a/doc/api/n-api.md b/doc/api/n-api.md index aab13420aab35f..220fd2b002be33 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -323,6 +323,31 @@ code needs to create an Error object: [`napi_create_error`][], where result is the napi_value that refers to the newly created JavaScript Error object. +The Node.js project is adding error codes to all of the errors +generated internally. The goal is for applications to use these +error codes for all error checking. The associated error messages +will remain, but will only be meant to be used for logging and +display with the expectation that the message can change without +SemVer applying. In order to support this model with N-API, both +in internal functionality and for module specific functionality +(as its good practice), the `throw_` and `create_` functions +take an optional code parameter which is the string for the code +to be added to the error object. If the optional parameter is NULL +then no code will be associated with the error. If a code is provided, +the name associated with the error is also updated to be: + +``` +originalName [code] +``` + +where originalName is the original name associated with the error +and code is the code that was provided. For example if the code +is 'ERR_ERROR_1' and a TypeError is being created the name will be: + +``` +TypeError [ERR_ERROR_1] +``` + #### napi_throw ```C -NODE_EXTERN napi_status napi_throw_error(napi_env env, const char* msg); +NODE_EXTERN napi_status napi_throw_error(napi_env env, + const char* code, + const char* msg); ``` - `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional error code to be set on the error. - `[in] msg`: C string representing the text to be associated with the error. @@ -358,9 +386,12 @@ This API throws a JavaScript Error with the text provided. added: v8.0.0 --> ```C -NODE_EXTERN napi_status napi_throw_type_error(napi_env env, const char* msg); +NODE_EXTERN napi_status napi_throw_type_error(napi_env env, + const char* code, + const char* msg); ``` - `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional error code to be set on the error. - `[in] msg`: C string representing the text to be associated with the error. @@ -373,9 +404,12 @@ This API throws a JavaScript TypeError with the text provided. added: v8.0.0 --> ```C -NODE_EXTERN napi_status napi_throw_range_error(napi_env env, const char* msg); +NODE_EXTERN napi_status napi_throw_range_error(napi_env env, + const char* code, + const char* msg); ``` - `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional error code to be set on the error. - `[in] msg`: C string representing the text to be associated with the error. @@ -409,10 +443,13 @@ added: v8.0.0 --> ```C NODE_EXTERN napi_status napi_create_error(napi_env env, + napi_value code, napi_value msg, napi_value* result); ``` - `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional `napi_value` with the string for the error code to + be associated with the error. - `[in] msg`: napi_value that references a JavaScript String to be used as the message for the Error. - `[out] result`: `napi_value` representing the error created. @@ -427,10 +464,13 @@ added: v8.0.0 --> ```C NODE_EXTERN napi_status napi_create_type_error(napi_env env, + napi_value code, napi_value msg, napi_value* result); ``` - `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional `napi_value` with the string for the error code to + be associated with the error. - `[in] msg`: napi_value that references a JavaScript String to be used as the message for the Error. - `[out] result`: `napi_value` representing the error created. @@ -446,10 +486,13 @@ added: v8.0.0 --> ```C NODE_EXTERN napi_status napi_create_range_error(napi_env env, + napi_value code, const char* msg, napi_value* result); ``` - `[in] env`: The environment that the API is invoked under. +- `[in] code`: Optional `napi_value` with the string for the error code to + be associated with the error. - `[in] msg`: napi_value that references a JavaScript String to be used as the message for the Error. - `[out] result`: `napi_value` representing the error created. diff --git a/lib/internal/errors.js b/lib/internal/errors.js index a94758bde1ed4a..435db9e2bfd5e6 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -161,6 +161,8 @@ E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe'); E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks'); E('ERR_MISSING_ARGS', missingArgs); E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times'); +E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function'); +E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object'); E('ERR_NO_CRYPTO', 'Node.js is not compiled with OpenSSL crypto support'); E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s'); E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound'); diff --git a/src/node_api.cc b/src/node_api.cc index becafc4684925c..f9119e6a66924a 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -18,6 +18,7 @@ #include "uv.h" #include "node_api.h" #include "node_internals.h" +#include "util.h" #define NAPI_VERSION 1 @@ -1522,7 +1523,61 @@ napi_status napi_create_symbol(napi_env env, return GET_RETURN_STATUS(env); } +static napi_status set_error_code(napi_env env, + v8::Local error, + napi_value code, + const char* code_cstring) { + if ((code != nullptr) || (code_cstring != nullptr)) { + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local err_object = error.As(); + + v8::Local code_value = v8impl::V8LocalValueFromJsValue(code); + if (code != nullptr) { + code_value = v8impl::V8LocalValueFromJsValue(code); + RETURN_STATUS_IF_FALSE(env, code_value->IsString(), napi_string_expected); + } else { + CHECK_NEW_FROM_UTF8(env, code_value, code_cstring); + } + + v8::Local code_key; + CHECK_NEW_FROM_UTF8(env, code_key, "code"); + + v8::Maybe set_maybe = err_object->Set(context, code_key, code_value); + RETURN_STATUS_IF_FALSE(env, + set_maybe.FromMaybe(false), + napi_generic_failure); + + // now update the name to be "name [code]" where name is the + // original name and code is the code associated with the Error + v8::Local name_string; + CHECK_NEW_FROM_UTF8(env, name_string, ""); + v8::Local name_key; + CHECK_NEW_FROM_UTF8(env, name_key, "name"); + + auto maybe_name = err_object->Get(context, name_key); + if (!maybe_name.IsEmpty()) { + v8::Local name = maybe_name.ToLocalChecked(); + if (name->IsString()) { + name_string = v8::String::Concat(name_string, name.As()); + } + } + name_string = v8::String::Concat(name_string, + FIXED_ONE_BYTE_STRING(isolate, " [")); + name_string = v8::String::Concat(name_string, code_value.As()); + name_string = v8::String::Concat(name_string, + FIXED_ONE_BYTE_STRING(isolate, "]")); + + set_maybe = err_object->Set(context, name_key, name_string); + RETURN_STATUS_IF_FALSE(env, + set_maybe.FromMaybe(false), + napi_generic_failure); + } + return napi_ok; +} + napi_status napi_create_error(napi_env env, + napi_value code, napi_value msg, napi_value* result) { NAPI_PREAMBLE(env); @@ -1532,13 +1587,18 @@ napi_status napi_create_error(napi_env env, v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); - *result = v8impl::JsValueFromV8LocalValue(v8::Exception::Error( - message_value.As())); + v8::Local error_obj = + v8::Exception::Error(message_value.As()); + napi_status status = set_error_code(env, error_obj, code, nullptr); + if (status != napi_ok) return status; + + *result = v8impl::JsValueFromV8LocalValue(error_obj); return GET_RETURN_STATUS(env); } napi_status napi_create_type_error(napi_env env, + napi_value code, napi_value msg, napi_value* result) { NAPI_PREAMBLE(env); @@ -1548,13 +1608,18 @@ napi_status napi_create_type_error(napi_env env, v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); - *result = v8impl::JsValueFromV8LocalValue(v8::Exception::TypeError( - message_value.As())); + v8::Local error_obj = + v8::Exception::TypeError(message_value.As()); + napi_status status = set_error_code(env, error_obj, code, nullptr); + if (status != napi_ok) return status; + + *result = v8impl::JsValueFromV8LocalValue(error_obj); return GET_RETURN_STATUS(env); } napi_status napi_create_range_error(napi_env env, + napi_value code, napi_value msg, napi_value* result) { NAPI_PREAMBLE(env); @@ -1564,8 +1629,12 @@ napi_status napi_create_range_error(napi_env env, v8::Local message_value = v8impl::V8LocalValueFromJsValue(msg); RETURN_STATUS_IF_FALSE(env, message_value->IsString(), napi_string_expected); - *result = v8impl::JsValueFromV8LocalValue(v8::Exception::RangeError( - message_value.As())); + v8::Local error_obj = + v8::Exception::RangeError(message_value.As()); + napi_status status = set_error_code(env, error_obj, code, nullptr); + if (status != napi_ok) return status; + + *result = v8impl::JsValueFromV8LocalValue(error_obj); return GET_RETURN_STATUS(env); } @@ -1738,40 +1807,58 @@ napi_status napi_throw(napi_env env, napi_value error) { return napi_clear_last_error(env); } -napi_status napi_throw_error(napi_env env, const char* msg) { +napi_status napi_throw_error(napi_env env, + const char* code, + const char* msg) { NAPI_PREAMBLE(env); v8::Isolate* isolate = env->isolate; v8::Local str; CHECK_NEW_FROM_UTF8(env, str, msg); - isolate->ThrowException(v8::Exception::Error(str)); + v8::Local error_obj = v8::Exception::Error(str); + napi_status status = set_error_code(env, error_obj, nullptr, code); + if (status != napi_ok) return status; + + isolate->ThrowException(error_obj); // any VM calls after this point and before returning // to the javascript invoker will fail return napi_clear_last_error(env); } -napi_status napi_throw_type_error(napi_env env, const char* msg) { +napi_status napi_throw_type_error(napi_env env, + const char* code, + const char* msg) { NAPI_PREAMBLE(env); v8::Isolate* isolate = env->isolate; v8::Local str; CHECK_NEW_FROM_UTF8(env, str, msg); - isolate->ThrowException(v8::Exception::TypeError(str)); + v8::Local error_obj = v8::Exception::TypeError(str); + napi_status status = set_error_code(env, error_obj, nullptr, code); + if (status != napi_ok) return status; + + isolate->ThrowException(error_obj); // any VM calls after this point and before returning // to the javascript invoker will fail return napi_clear_last_error(env); } -napi_status napi_throw_range_error(napi_env env, const char* msg) { +napi_status napi_throw_range_error(napi_env env, + const char* code, + const char* msg) { NAPI_PREAMBLE(env); v8::Isolate* isolate = env->isolate; v8::Local str; CHECK_NEW_FROM_UTF8(env, str, msg); - isolate->ThrowException(v8::Exception::RangeError(str)); + v8::Local error_obj = v8::Exception::RangeError(str); + napi_status status = set_error_code(env, error_obj, nullptr, code); + if (status != napi_ok) return status; + + isolate->ThrowException(error_obj); // any VM calls after this point and before returning // to the javascript invoker will fail return napi_clear_last_error(env); @@ -2391,7 +2478,9 @@ napi_status napi_instanceof(napi_env env, CHECK_TO_OBJECT(env, context, ctor, constructor); if (!ctor->IsFunction()) { - napi_throw_type_error(env, "constructor must be a function"); + napi_throw_type_error(env, + "ERR_NAPI_CONS_FUNCTION", + "Constructor must be a function"); return napi_set_last_error(env, napi_function_expected); } @@ -2459,7 +2548,10 @@ napi_status napi_instanceof(napi_env env, v8::Local prototype_property = maybe_prototype.ToLocalChecked(); if (!prototype_property->IsObject()) { - napi_throw_type_error(env, "constructor.prototype must be an object"); + napi_throw_type_error( + env, + "ERR_NAPI_CONS_PROTOTYPE_OBJECT", + "Constructor.prototype must be an object"); return napi_set_last_error(env, napi_object_expected); } diff --git a/src/node_api.h b/src/node_api.h index e346b762dd2702..af98789d418210 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -142,12 +142,15 @@ NAPI_EXTERN napi_status napi_create_function(napi_env env, void* data, napi_value* result); NAPI_EXTERN napi_status napi_create_error(napi_env env, + napi_value code, napi_value msg, napi_value* result); NAPI_EXTERN napi_status napi_create_type_error(napi_env env, + napi_value code, napi_value msg, napi_value* result); NAPI_EXTERN napi_status napi_create_range_error(napi_env env, + napi_value code, napi_value msg, napi_value* result); @@ -404,9 +407,15 @@ NAPI_EXTERN napi_status napi_escape_handle(napi_env env, // Methods to support error handling NAPI_EXTERN napi_status napi_throw(napi_env env, napi_value error); -NAPI_EXTERN napi_status napi_throw_error(napi_env env, const char* msg); -NAPI_EXTERN napi_status napi_throw_type_error(napi_env env, const char* msg); -NAPI_EXTERN napi_status napi_throw_range_error(napi_env env, const char* msg); +NAPI_EXTERN napi_status napi_throw_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status napi_throw_type_error(napi_env env, + const char* code, + const char* msg); +NAPI_EXTERN napi_status napi_throw_range_error(napi_env env, + const char* code, + const char* msg); NAPI_EXTERN napi_status napi_is_error(napi_env env, napi_value value, bool* result); diff --git a/test/addons-napi/common.h b/test/addons-napi/common.h index e9640935d4154b..422418ced49a39 100644 --- a/test/addons-napi/common.h +++ b/test/addons-napi/common.h @@ -12,7 +12,7 @@ const char* error_message = error_info->error_message != NULL ? \ error_info->error_message : \ "empty error message"; \ - napi_throw_error((env), error_message); \ + napi_throw_error((env), NULL, error_message); \ } \ } while (0) @@ -21,6 +21,7 @@ if (!(assertion)) { \ napi_throw_error( \ (env), \ + NULL, \ "assertion (" #assertion ") failed: " message); \ return ret_val; \ } \ diff --git a/test/addons-napi/test_async/test_async.cc b/test/addons-napi/test_async/test_async.cc index cc7bc334304323..f257b268b93159 100644 --- a/test/addons-napi/test_async/test_async.cc +++ b/test/addons-napi/test_async/test_async.cc @@ -29,7 +29,7 @@ void Execute(napi_env env, void* data) { carrier* c = static_cast(data); if (c != &the_carrier) { - napi_throw_type_error(env, "Wrong data parameter to Execute."); + napi_throw_type_error(env, nullptr, "Wrong data parameter to Execute."); return; } @@ -40,12 +40,12 @@ void Complete(napi_env env, napi_status status, void* data) { carrier* c = static_cast(data); if (c != &the_carrier) { - napi_throw_type_error(env, "Wrong data parameter to Complete."); + napi_throw_type_error(env, nullptr, "Wrong data parameter to Complete."); return; } if (status != napi_ok) { - napi_throw_type_error(env, "Execute callback failed."); + napi_throw_type_error(env, nullptr, "Execute callback failed."); return; } diff --git a/test/addons-napi/test_error/test.js b/test/addons-napi/test_error/test.js index f7479f2c9a64d8..80f99f48ba79f3 100644 --- a/test/addons-napi/test_error/test.js +++ b/test/addons-napi/test_error/test.js @@ -72,6 +72,30 @@ assert.throws(() => { test_error.throwTypeError(); }, /^TypeError: type error$/); +assert.throws( + () => test_error.throwErrorCode(), + common.expectsError({ + code: 'ERR_TEST_CODE', + message: 'Error [error]' + }) +); + +assert.throws( + () => test_error.throwRangeErrorCode(), + common.expectsError({ + code: 'ERR_TEST_CODE', + message: 'RangeError [range error]' + }) +); + +assert.throws( + () => test_error.throwTypeErrorCode(), + common.expectsError({ + code: 'ERR_TEST_CODE', + message: 'TypeError [type error]' + }) +); + let error = test_error.createError(); assert.ok(error instanceof Error, 'expected error to be an instance of Error'); assert.strictEqual(error.message, 'error', 'expected message to be "error"'); @@ -89,3 +113,41 @@ assert.ok(error instanceof TypeError, assert.strictEqual(error.message, 'type error', 'expected message to be "type error"'); + +error = test_error.createErrorCode(); +assert.ok(error instanceof Error, 'expected error to be an instance of Error'); +assert.strictEqual(error.code, + 'ERR_TEST_CODE', + 'expected code to be "ERR_TEST_CODE"'); +assert.strictEqual(error.message, + 'Error [error]', + 'expected message to be "Error [error]"'); +assert.strictEqual(error.name, + 'Error [ERR_TEST_CODE]', + 'expected name to be "Error [ERR_TEST_CODE]"'); + +error = test_error.createRangeErrorCode(); +assert.ok(error instanceof RangeError, + 'expected error to be an instance of RangeError'); +assert.strictEqual(error.message, + 'RangeError [range error]', + 'expected message to be "RangeError [range error]"'); +assert.strictEqual(error.code, + 'ERR_TEST_CODE', + 'expected code to be "ERR_TEST_CODE"'); +assert.strictEqual(error.name, + 'RangeError [ERR_TEST_CODE]', + 'expected name to be "RangeError[ERR_TEST_CODE]"'); + +error = test_error.createTypeErrorCode(); +assert.ok(error instanceof TypeError, + 'expected error to be an instance of TypeError'); +assert.strictEqual(error.message, + 'TypeError [type error]', + 'expected message to be "TypeError [type error]"'); +assert.strictEqual(error.code, + 'ERR_TEST_CODE', + 'expected code to be "ERR_TEST_CODE"'); +assert.strictEqual(error.name, + 'TypeError [ERR_TEST_CODE]', + 'expected name to be "TypeError[ERR_TEST_CODE]"'); diff --git a/test/addons-napi/test_error/test_error.cc b/test/addons-napi/test_error/test_error.cc index ddba2059f23be6..29aba1f1ef62f6 100644 --- a/test/addons-napi/test_error/test_error.cc +++ b/test/addons-napi/test_error/test_error.cc @@ -19,31 +19,51 @@ napi_value throwExistingError(napi_env env, napi_callback_info info) { napi_value message; napi_value error; NAPI_CALL(env, napi_create_string_utf8(env, "existing error", -1, &message)); - NAPI_CALL(env, napi_create_error(env, message, &error)); + NAPI_CALL(env, napi_create_error(env, nullptr, message, &error)); NAPI_CALL(env, napi_throw(env, error)); return nullptr; } napi_value throwError(napi_env env, napi_callback_info info) { - NAPI_CALL(env, napi_throw_error(env, "error")); + NAPI_CALL(env, napi_throw_error(env, nullptr, "error")); return nullptr; } napi_value throwRangeError(napi_env env, napi_callback_info info) { - NAPI_CALL(env, napi_throw_range_error(env, "range error")); + NAPI_CALL(env, napi_throw_range_error(env, nullptr, "range error")); return nullptr; } napi_value throwTypeError(napi_env env, napi_callback_info info) { - NAPI_CALL(env, napi_throw_type_error(env, "type error")); + NAPI_CALL(env, napi_throw_type_error(env, nullptr, "type error")); return nullptr; } +napi_value throwErrorCode(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_error(env, "ERR_TEST_CODE", "Error [error]")); + return nullptr; +} + +napi_value throwRangeErrorCode(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_range_error(env, + "ERR_TEST_CODE", + "RangeError [range error]")); + return nullptr; +} + +napi_value throwTypeErrorCode(napi_env env, napi_callback_info info) { + NAPI_CALL(env, napi_throw_type_error(env, + "ERR_TEST_CODE", + "TypeError [type error]")); + return nullptr; +} + + napi_value createError(napi_env env, napi_callback_info info) { napi_value result; napi_value message; NAPI_CALL(env, napi_create_string_utf8(env, "error", -1, &message)); - NAPI_CALL(env, napi_create_error(env, message, &result)); + NAPI_CALL(env, napi_create_error(env, nullptr, message, &result)); return result; } @@ -51,7 +71,7 @@ napi_value createRangeError(napi_env env, napi_callback_info info) { napi_value result; napi_value message; NAPI_CALL(env, napi_create_string_utf8(env, "range error", -1, &message)); - NAPI_CALL(env, napi_create_range_error(env, message, &result)); + NAPI_CALL(env, napi_create_range_error(env, nullptr, message, &result)); return result; } @@ -59,7 +79,43 @@ napi_value createTypeError(napi_env env, napi_callback_info info) { napi_value result; napi_value message; NAPI_CALL(env, napi_create_string_utf8(env, "type error", -1, &message)); - NAPI_CALL(env, napi_create_type_error(env, message, &result)); + NAPI_CALL(env, napi_create_type_error(env, nullptr, message, &result)); + return result; +} + +napi_value createErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NAPI_CALL(env, napi_create_string_utf8(env, "Error [error]", -1, &message)); + NAPI_CALL(env, napi_create_string_utf8(env, "ERR_TEST_CODE", -1, &code)); + NAPI_CALL(env, napi_create_error(env, code, message, &result)); + return result; +} + +napi_value createRangeErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NAPI_CALL(env, napi_create_string_utf8(env, + "RangeError [range error]", + -1, + &message)); + NAPI_CALL(env, napi_create_string_utf8(env, "ERR_TEST_CODE", -1, &code)); + NAPI_CALL(env, napi_create_range_error(env, code, message, &result)); + return result; +} + +napi_value createTypeErrorCode(napi_env env, napi_callback_info info) { + napi_value result; + napi_value message; + napi_value code; + NAPI_CALL(env, napi_create_string_utf8(env, + "TypeError [type error]", + -1, + &message)); + NAPI_CALL(env, napi_create_string_utf8(env, "ERR_TEST_CODE", -1, &code)); + NAPI_CALL(env, napi_create_type_error(env, code, message, &result)); return result; } @@ -70,9 +126,15 @@ void Init(napi_env env, napi_value exports, napi_value module, void* priv) { DECLARE_NAPI_PROPERTY("throwError", throwError), DECLARE_NAPI_PROPERTY("throwRangeError", throwRangeError), DECLARE_NAPI_PROPERTY("throwTypeError", throwTypeError), + DECLARE_NAPI_PROPERTY("throwErrorCode", throwErrorCode), + DECLARE_NAPI_PROPERTY("throwRangeErrorCode", throwRangeErrorCode), + DECLARE_NAPI_PROPERTY("throwTypeErrorCode", throwTypeErrorCode), DECLARE_NAPI_PROPERTY("createError", createError), DECLARE_NAPI_PROPERTY("createRangeError", createRangeError), DECLARE_NAPI_PROPERTY("createTypeError", createTypeError), + DECLARE_NAPI_PROPERTY("createErrorCode", createErrorCode), + DECLARE_NAPI_PROPERTY("createRangeErrorCode", createRangeErrorCode), + DECLARE_NAPI_PROPERTY("createTypeErrorCode", createTypeErrorCode), }; NAPI_CALL_RETURN_VOID(env, napi_define_properties( diff --git a/test/addons-napi/test_typedarray/test_typedarray.c b/test/addons-napi/test_typedarray/test_typedarray.c index 4194492cae8b3a..ffc118681bad9e 100644 --- a/test/addons-napi/test_typedarray/test_typedarray.c +++ b/test/addons-napi/test_typedarray/test_typedarray.c @@ -65,7 +65,7 @@ napi_value Multiply(napi_env env, napi_callback_info info) { output_doubles[i] = input_doubles[i] * multiplier; } } else { - napi_throw_error(env, "Typed array was of a type not expected by test."); + napi_throw_error(env, NULL, "Typed array was of a type not expected by test."); return NULL; }