diff --git a/std/node/_errors.ts b/std/node/_errors.ts index 2321bb24f4..864f1252b0 100644 --- a/std/node/_errors.ts +++ b/std/node/_errors.ts @@ -21,7 +21,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +import { unreachable } from "../testing/asserts.ts"; + // It will do so until we'll have Node errors completely ported (#5944): + // Ref: https://github.com/nodejs/node/blob/50d28d4b3a616b04537feff014aa70437f064e30/lib/internal/errors.js#L251 // Ref: https://github.com/nodejs/node/blob/50d28d4b3a616b04537feff014aa70437f064e30/lib/internal/errors.js#L299 // Ref: https://github.com/nodejs/node/blob/50d28d4b3a616b04537feff014aa70437f064e30/lib/internal/errors.js#L325 @@ -43,6 +46,299 @@ class ERR_INVALID_ARG_TYPE extends TypeError { } } +class ERR_OUT_OF_RANGE extends RangeError { + code = "ERR_OUT_OF_RANGE"; + + constructor(str: string, range: string, received: unknown) { + super( + `The value of "${str}" is out of range. It must be ${range}. Received ${received}`, + ); + + const { name } = this; + // Add the error code to the name to include it in the stack trace. + this.name = `${name} [${this.code}]`; + // Access the stack to generate the error message including the error code from the name. + this.stack; + // Reset the name to the actual name. + this.name = name; + } +} + export const codes = { ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, }; + +// In Node these values are coming from libuv: +// Ref: https://github.com/libuv/libuv/blob/v1.x/include/uv/errno.h +// Ref: https://github.com/nodejs/node/blob/524123fbf064ff64bb6fcd83485cfc27db932f68/lib/internal/errors.js#L383 +// Since there is no easy way to port code from libuv and these maps are +// changing very rarely, we simply extract them from Node and store here. + +// Note +// Run the following to get the map: +// $ node -e "console.log(process.binding('uv').getErrorMap())" +// This setup automatically exports maps from both "win", "linux" & darwin: +// https://github.com/schwarzkopfb/node_errno_map + +type ErrMapData = Array<[number, [string, string]]>; + +const windows: ErrMapData = [ + [-4093, ["E2BIG", "argument list too long"]], + [-4092, ["EACCES", "permission denied"]], + [-4091, ["EADDRINUSE", "address already in use"]], + [-4090, ["EADDRNOTAVAIL", "address not available"]], + [-4089, ["EAFNOSUPPORT", "address family not supported"]], + [-4088, ["EAGAIN", "resource temporarily unavailable"]], + [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], + [-3001, ["EAI_AGAIN", "temporary failure"]], + [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], + [-3013, ["EAI_BADHINTS", "invalid value for hints"]], + [-3003, ["EAI_CANCELED", "request canceled"]], + [-3004, ["EAI_FAIL", "permanent failure"]], + [-3005, ["EAI_FAMILY", "ai_family not supported"]], + [-3006, ["EAI_MEMORY", "out of memory"]], + [-3007, ["EAI_NODATA", "no address"]], + [-3008, ["EAI_NONAME", "unknown node or service"]], + [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], + [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], + [-3010, ["EAI_SERVICE", "service not available for socket type"]], + [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], + [-4084, ["EALREADY", "connection already in progress"]], + [-4083, ["EBADF", "bad file descriptor"]], + [-4082, ["EBUSY", "resource busy or locked"]], + [-4081, ["ECANCELED", "operation canceled"]], + [-4080, ["ECHARSET", "invalid Unicode character"]], + [-4079, ["ECONNABORTED", "software caused connection abort"]], + [-4078, ["ECONNREFUSED", "connection refused"]], + [-4077, ["ECONNRESET", "connection reset by peer"]], + [-4076, ["EDESTADDRREQ", "destination address required"]], + [-4075, ["EEXIST", "file already exists"]], + [-4074, ["EFAULT", "bad address in system call argument"]], + [-4036, ["EFBIG", "file too large"]], + [-4073, ["EHOSTUNREACH", "host is unreachable"]], + [-4072, ["EINTR", "interrupted system call"]], + [-4071, ["EINVAL", "invalid argument"]], + [-4070, ["EIO", "i/o error"]], + [-4069, ["EISCONN", "socket is already connected"]], + [-4068, ["EISDIR", "illegal operation on a directory"]], + [-4067, ["ELOOP", "too many symbolic links encountered"]], + [-4066, ["EMFILE", "too many open files"]], + [-4065, ["EMSGSIZE", "message too long"]], + [-4064, ["ENAMETOOLONG", "name too long"]], + [-4063, ["ENETDOWN", "network is down"]], + [-4062, ["ENETUNREACH", "network is unreachable"]], + [-4061, ["ENFILE", "file table overflow"]], + [-4060, ["ENOBUFS", "no buffer space available"]], + [-4059, ["ENODEV", "no such device"]], + [-4058, ["ENOENT", "no such file or directory"]], + [-4057, ["ENOMEM", "not enough memory"]], + [-4056, ["ENONET", "machine is not on the network"]], + [-4035, ["ENOPROTOOPT", "protocol not available"]], + [-4055, ["ENOSPC", "no space left on device"]], + [-4054, ["ENOSYS", "function not implemented"]], + [-4053, ["ENOTCONN", "socket is not connected"]], + [-4052, ["ENOTDIR", "not a directory"]], + [-4051, ["ENOTEMPTY", "directory not empty"]], + [-4050, ["ENOTSOCK", "socket operation on non-socket"]], + [-4049, ["ENOTSUP", "operation not supported on socket"]], + [-4048, ["EPERM", "operation not permitted"]], + [-4047, ["EPIPE", "broken pipe"]], + [-4046, ["EPROTO", "protocol error"]], + [-4045, ["EPROTONOSUPPORT", "protocol not supported"]], + [-4044, ["EPROTOTYPE", "protocol wrong type for socket"]], + [-4034, ["ERANGE", "result too large"]], + [-4043, ["EROFS", "read-only file system"]], + [-4042, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], + [-4041, ["ESPIPE", "invalid seek"]], + [-4040, ["ESRCH", "no such process"]], + [-4039, ["ETIMEDOUT", "connection timed out"]], + [-4038, ["ETXTBSY", "text file is busy"]], + [-4037, ["EXDEV", "cross-device link not permitted"]], + [-4094, ["UNKNOWN", "unknown error"]], + [-4095, ["EOF", "end of file"]], + [-4033, ["ENXIO", "no such device or address"]], + [-4032, ["EMLINK", "too many links"]], + [-4031, ["EHOSTDOWN", "host is down"]], + [-4030, ["EREMOTEIO", "remote I/O error"]], + [-4029, ["ENOTTY", "inappropriate ioctl for device"]], + [-4028, ["EFTYPE", "inappropriate file type or format"]], + [-4027, ["EILSEQ", "illegal byte sequence"]], +]; + +const darwin: ErrMapData = [ + [-7, ["E2BIG", "argument list too long"]], + [-13, ["EACCES", "permission denied"]], + [-48, ["EADDRINUSE", "address already in use"]], + [-49, ["EADDRNOTAVAIL", "address not available"]], + [-47, ["EAFNOSUPPORT", "address family not supported"]], + [-35, ["EAGAIN", "resource temporarily unavailable"]], + [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], + [-3001, ["EAI_AGAIN", "temporary failure"]], + [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], + [-3013, ["EAI_BADHINTS", "invalid value for hints"]], + [-3003, ["EAI_CANCELED", "request canceled"]], + [-3004, ["EAI_FAIL", "permanent failure"]], + [-3005, ["EAI_FAMILY", "ai_family not supported"]], + [-3006, ["EAI_MEMORY", "out of memory"]], + [-3007, ["EAI_NODATA", "no address"]], + [-3008, ["EAI_NONAME", "unknown node or service"]], + [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], + [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], + [-3010, ["EAI_SERVICE", "service not available for socket type"]], + [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], + [-37, ["EALREADY", "connection already in progress"]], + [-9, ["EBADF", "bad file descriptor"]], + [-16, ["EBUSY", "resource busy or locked"]], + [-89, ["ECANCELED", "operation canceled"]], + [-4080, ["ECHARSET", "invalid Unicode character"]], + [-53, ["ECONNABORTED", "software caused connection abort"]], + [-61, ["ECONNREFUSED", "connection refused"]], + [-54, ["ECONNRESET", "connection reset by peer"]], + [-39, ["EDESTADDRREQ", "destination address required"]], + [-17, ["EEXIST", "file already exists"]], + [-14, ["EFAULT", "bad address in system call argument"]], + [-27, ["EFBIG", "file too large"]], + [-65, ["EHOSTUNREACH", "host is unreachable"]], + [-4, ["EINTR", "interrupted system call"]], + [-22, ["EINVAL", "invalid argument"]], + [-5, ["EIO", "i/o error"]], + [-56, ["EISCONN", "socket is already connected"]], + [-21, ["EISDIR", "illegal operation on a directory"]], + [-62, ["ELOOP", "too many symbolic links encountered"]], + [-24, ["EMFILE", "too many open files"]], + [-40, ["EMSGSIZE", "message too long"]], + [-63, ["ENAMETOOLONG", "name too long"]], + [-50, ["ENETDOWN", "network is down"]], + [-51, ["ENETUNREACH", "network is unreachable"]], + [-23, ["ENFILE", "file table overflow"]], + [-55, ["ENOBUFS", "no buffer space available"]], + [-19, ["ENODEV", "no such device"]], + [-2, ["ENOENT", "no such file or directory"]], + [-12, ["ENOMEM", "not enough memory"]], + [-4056, ["ENONET", "machine is not on the network"]], + [-42, ["ENOPROTOOPT", "protocol not available"]], + [-28, ["ENOSPC", "no space left on device"]], + [-78, ["ENOSYS", "function not implemented"]], + [-57, ["ENOTCONN", "socket is not connected"]], + [-20, ["ENOTDIR", "not a directory"]], + [-66, ["ENOTEMPTY", "directory not empty"]], + [-38, ["ENOTSOCK", "socket operation on non-socket"]], + [-45, ["ENOTSUP", "operation not supported on socket"]], + [-1, ["EPERM", "operation not permitted"]], + [-32, ["EPIPE", "broken pipe"]], + [-100, ["EPROTO", "protocol error"]], + [-43, ["EPROTONOSUPPORT", "protocol not supported"]], + [-41, ["EPROTOTYPE", "protocol wrong type for socket"]], + [-34, ["ERANGE", "result too large"]], + [-30, ["EROFS", "read-only file system"]], + [-58, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], + [-29, ["ESPIPE", "invalid seek"]], + [-3, ["ESRCH", "no such process"]], + [-60, ["ETIMEDOUT", "connection timed out"]], + [-26, ["ETXTBSY", "text file is busy"]], + [-18, ["EXDEV", "cross-device link not permitted"]], + [-4094, ["UNKNOWN", "unknown error"]], + [-4095, ["EOF", "end of file"]], + [-6, ["ENXIO", "no such device or address"]], + [-31, ["EMLINK", "too many links"]], + [-64, ["EHOSTDOWN", "host is down"]], + [-4030, ["EREMOTEIO", "remote I/O error"]], + [-25, ["ENOTTY", "inappropriate ioctl for device"]], + [-79, ["EFTYPE", "inappropriate file type or format"]], + [-92, ["EILSEQ", "illegal byte sequence"]], +]; + +const linux: ErrMapData = [ + [-7, ["E2BIG", "argument list too long"]], + [-13, ["EACCES", "permission denied"]], + [-98, ["EADDRINUSE", "address already in use"]], + [-99, ["EADDRNOTAVAIL", "address not available"]], + [-97, ["EAFNOSUPPORT", "address family not supported"]], + [-11, ["EAGAIN", "resource temporarily unavailable"]], + [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], + [-3001, ["EAI_AGAIN", "temporary failure"]], + [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], + [-3013, ["EAI_BADHINTS", "invalid value for hints"]], + [-3003, ["EAI_CANCELED", "request canceled"]], + [-3004, ["EAI_FAIL", "permanent failure"]], + [-3005, ["EAI_FAMILY", "ai_family not supported"]], + [-3006, ["EAI_MEMORY", "out of memory"]], + [-3007, ["EAI_NODATA", "no address"]], + [-3008, ["EAI_NONAME", "unknown node or service"]], + [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], + [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], + [-3010, ["EAI_SERVICE", "service not available for socket type"]], + [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], + [-114, ["EALREADY", "connection already in progress"]], + [-9, ["EBADF", "bad file descriptor"]], + [-16, ["EBUSY", "resource busy or locked"]], + [-125, ["ECANCELED", "operation canceled"]], + [-4080, ["ECHARSET", "invalid Unicode character"]], + [-103, ["ECONNABORTED", "software caused connection abort"]], + [-111, ["ECONNREFUSED", "connection refused"]], + [-104, ["ECONNRESET", "connection reset by peer"]], + [-89, ["EDESTADDRREQ", "destination address required"]], + [-17, ["EEXIST", "file already exists"]], + [-14, ["EFAULT", "bad address in system call argument"]], + [-27, ["EFBIG", "file too large"]], + [-113, ["EHOSTUNREACH", "host is unreachable"]], + [-4, ["EINTR", "interrupted system call"]], + [-22, ["EINVAL", "invalid argument"]], + [-5, ["EIO", "i/o error"]], + [-106, ["EISCONN", "socket is already connected"]], + [-21, ["EISDIR", "illegal operation on a directory"]], + [-40, ["ELOOP", "too many symbolic links encountered"]], + [-24, ["EMFILE", "too many open files"]], + [-90, ["EMSGSIZE", "message too long"]], + [-36, ["ENAMETOOLONG", "name too long"]], + [-100, ["ENETDOWN", "network is down"]], + [-101, ["ENETUNREACH", "network is unreachable"]], + [-23, ["ENFILE", "file table overflow"]], + [-105, ["ENOBUFS", "no buffer space available"]], + [-19, ["ENODEV", "no such device"]], + [-2, ["ENOENT", "no such file or directory"]], + [-12, ["ENOMEM", "not enough memory"]], + [-64, ["ENONET", "machine is not on the network"]], + [-92, ["ENOPROTOOPT", "protocol not available"]], + [-28, ["ENOSPC", "no space left on device"]], + [-38, ["ENOSYS", "function not implemented"]], + [-107, ["ENOTCONN", "socket is not connected"]], + [-20, ["ENOTDIR", "not a directory"]], + [-39, ["ENOTEMPTY", "directory not empty"]], + [-88, ["ENOTSOCK", "socket operation on non-socket"]], + [-95, ["ENOTSUP", "operation not supported on socket"]], + [-1, ["EPERM", "operation not permitted"]], + [-32, ["EPIPE", "broken pipe"]], + [-71, ["EPROTO", "protocol error"]], + [-93, ["EPROTONOSUPPORT", "protocol not supported"]], + [-91, ["EPROTOTYPE", "protocol wrong type for socket"]], + [-34, ["ERANGE", "result too large"]], + [-30, ["EROFS", "read-only file system"]], + [-108, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], + [-29, ["ESPIPE", "invalid seek"]], + [-3, ["ESRCH", "no such process"]], + [-110, ["ETIMEDOUT", "connection timed out"]], + [-26, ["ETXTBSY", "text file is busy"]], + [-18, ["EXDEV", "cross-device link not permitted"]], + [-4094, ["UNKNOWN", "unknown error"]], + [-4095, ["EOF", "end of file"]], + [-6, ["ENXIO", "no such device or address"]], + [-31, ["EMLINK", "too many links"]], + [-112, ["EHOSTDOWN", "host is down"]], + [-121, ["EREMOTEIO", "remote I/O error"]], + [-25, ["ENOTTY", "inappropriate ioctl for device"]], + [-4028, ["EFTYPE", "inappropriate file type or format"]], + [-84, ["EILSEQ", "illegal byte sequence"]], +]; + +const { os } = Deno.build; +export const errorMap = new Map( + os === "windows" + ? windows + : os === "darwin" + ? darwin + : os === "linux" + ? linux + : unreachable(), +); diff --git a/std/node/util.ts b/std/node/util.ts index 27aa5a12b2..076a5b830b 100644 --- a/std/node/util.ts +++ b/std/node/util.ts @@ -1,10 +1,16 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. export { promisify } from "./_util/_util_promisify.ts"; export { callbackify } from "./_util/_util_callbackify.ts"; +import { codes, errorMap } from "./_errors.ts"; import * as types from "./_util/_util_types.ts"; - export { types }; +const NumberIsSafeInteger = Number.isSafeInteger; +const { + ERR_OUT_OF_RANGE, + ERR_INVALID_ARG_TYPE, +} = codes; + const DEFAULT_INSPECT_OPTIONS = { showHidden: false, depth: 2, @@ -90,6 +96,16 @@ export function isPrimitive(value: unknown): boolean { ); } +export function getSystemErrorName(code: number): string | undefined { + if (typeof code !== "number") { + throw new ERR_INVALID_ARG_TYPE("err", "number", code); + } + if (code >= 0 || !NumberIsSafeInteger(code)) { + throw new ERR_OUT_OF_RANGE("err", "a negative integer", code); + } + return errorMap.get(code)?.[0]; +} + import { _TextDecoder, _TextEncoder } from "./_utils.ts"; /** The global TextDecoder */ diff --git a/std/node/util_test.ts b/std/node/util_test.ts index 9b55d01785..9db463b99e 100644 --- a/std/node/util_test.ts +++ b/std/node/util_test.ts @@ -1,5 +1,11 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { assert, assertEquals } from "../testing/asserts.ts"; + +import { + assert, + assertEquals, + assertStrictEquals, + assertThrows, +} from "../testing/asserts.ts"; import { stripColor } from "../fmt/colors.ts"; import * as util from "./util.ts"; @@ -182,3 +188,35 @@ Deno.test({ assert(util.types.isDate(new Date())); }, }); + +Deno.test({ + name: "[util] getSystemErrorName()", + fn() { + type FnTestInvalidArg = (code?: unknown) => void; + + assertThrows( + () => (util.getSystemErrorName as FnTestInvalidArg)(), + TypeError, + ); + assertThrows( + () => (util.getSystemErrorName as FnTestInvalidArg)(1), + RangeError, + ); + + assertStrictEquals(util.getSystemErrorName(-424242), undefined); + + switch (Deno.build.os) { + case "windows": + assertStrictEquals(util.getSystemErrorName(-4091), "EADDRINUSE"); + break; + + case "darwin": + assertStrictEquals(util.getSystemErrorName(-48), "EADDRINUSE"); + break; + + case "linux": + assertStrictEquals(util.getSystemErrorName(-98), "EADDRINUSE"); + break; + } + }, +});