Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 162 additions & 1 deletion node/_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@
* *********** */

import { getSystemErrorName, inspect } from "./util.ts";
import { codeMap, errorMap } from "./internal_binding/uv.ts";
import {
codeMap,
errorMap,
mapSysErrnoToUvErrno,
} from "./internal_binding/uv.ts";
import { assert } from "../_util/assert.ts";
import { fileURLToPath } from "./url.ts";

export { errorMap };

const kIsNodeError = Symbol("kIsNodeError");

/**
* @see https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js
*/
Expand Down Expand Up @@ -371,6 +377,118 @@ export class NodeURIError extends NodeErrorAbstraction implements URIError {
}
}

interface NodeSystemErrorCtx {
code: string;
syscall: string;
message: string;
errno: number;
path?: string;
dest?: string;
}
// A specialized Error that includes an additional info property with
// additional information about the error condition.
// It has the properties present in a UVException but with a custom error
// message followed by the uv error code and uv error message.
// It also has its own error code with the original uv error context put into
// `err.info`.
// The context passed into this error must have .code, .syscall and .message,
// and may have .path and .dest.
class NodeSystemError extends NodeErrorAbstraction {
constructor(key: string, context: NodeSystemErrorCtx, msgPrefix: string) {
let message = `${msgPrefix}: ${context.syscall} returned ` +
`${context.code} (${context.message})`;

if (context.path !== undefined) {
message += ` ${context.path}`;
}
if (context.dest !== undefined) {
message += ` => ${context.dest}`;
}

super("SystemError", key, message);

captureLargerStackTrace(this);

Object.defineProperties(this, {
[kIsNodeError]: {
value: true,
enumerable: false,
writable: false,
configurable: true,
},
info: {
value: context,
enumerable: true,
configurable: true,
writable: false,
},
errno: {
get() {
return context.errno;
},
set: (value) => {
context.errno = value;
},
enumerable: true,
configurable: true,
},
syscall: {
get() {
return context.syscall;
},
set: (value) => {
context.syscall = value;
},
enumerable: true,
configurable: true,
},
});

if (context.path !== undefined) {
Object.defineProperty(this, "path", {
get() {
return context.path;
},
set: (value) => {
context.path = value;
},
enumerable: true,
configurable: true,
});
}

if (context.dest !== undefined) {
Object.defineProperty(this, "dest", {
get() {
return context.dest;
},
set: (value) => {
context.dest = value;
},
enumerable: true,
configurable: true,
});
}
}

toString() {
return `${this.name} [${this.code}]: ${this.message}`;
}
}

function makeSystemErrorWithCode(key: string, msgPrfix: string) {
return class NodeError extends NodeSystemError {
constructor(ctx: NodeSystemErrorCtx) {
super(key, ctx, msgPrfix);
}
};
}

export const ERR_FS_EISDIR = makeSystemErrorWithCode(
"ERR_FS_EISDIR",
"Path is a directory",
);

export class ERR_INVALID_ARG_TYPE extends NodeTypeError {
constructor(name: string, expected: string | string[], actual: unknown) {
// https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js#L1037-L1087
Expand Down Expand Up @@ -2573,3 +2691,46 @@ export class ERR_PACKAGE_PATH_NOT_EXPORTED extends NodeError {
super("ERR_PACKAGE_PATH_NOT_EXPORTED", msg);
}
}

export class ERR_INTERNAL_ASSERTION extends NodeError {
constructor(
message?: string,
) {
const suffix = "This is caused by either a bug in Node.js " +
"or incorrect usage of Node.js internals.\n" +
"Please open an issue with this stack trace at " +
"https://github.com/nodejs/node/issues\n";
super(
"ERR_INTERNAL_ASSERTION",
message === undefined ? suffix : `${message}\n${suffix}`,
);
}
}

interface UvExceptionContext {
syscall: string;
}
export function denoErrorToNodeError(e: Error, ctx: UvExceptionContext) {
const errno = extractOsErrorNumberFromErrorMessage(e);
if (typeof errno === "undefined") {
return e;
}

const ex = uvException({
errno: mapSysErrnoToUvErrno(errno),
...ctx,
});
return ex;
}

function extractOsErrorNumberFromErrorMessage(e: unknown): number | undefined {
const match = e instanceof Error
? e.message.match(/\(os error (\d+)\)/)
: false;

if (match) {
return +match[1];
}

return undefined;
}
44 changes: 34 additions & 10 deletions node/_fs/_fs_rm.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
import {
validateRmOptions,
validateRmOptionsSync,
} from "../internal/fs/utils.js";
import { denoErrorToNodeError } from "../_errors.ts";
type rmOptions = {
force?: boolean;
maxRetries?: number;
recursive?: boolean;
retryDelay?: number;
};

type rmCallback = (err?: Error) => void;
type rmCallback = (err: Error | null) => void;

export function rm(path: string | URL, callback: rmCallback): void;
export function rm(
Expand All @@ -28,23 +33,42 @@ export function rm(

if (!callback) throw new Error("No callback function supplied");

Deno.remove(path, { recursive: options?.recursive })
.then((_) => callback(), (err) => {
if (options?.force && err instanceof Deno.errors.NotFound) {
callback();
} else {
callback(err);
validateRmOptions(
path,
options,
false,
(err: Error | null, options: rmOptions) => {
if (err) {
return callback(err);
}
});
Deno.remove(path, { recursive: options?.recursive })
.then((_) => callback(null), (err: unknown) => {
if (options?.force && err instanceof Deno.errors.NotFound) {
callback(null);
} else {
callback(
err instanceof Error
? denoErrorToNodeError(err, { syscall: "rm" })
: err,
);
}
});
},
);
}

export function rmSync(path: string | URL, options?: rmOptions) {
options = validateRmOptionsSync(path, options, false);
try {
Deno.removeSync(path, { recursive: options?.recursive });
} catch (err) {
} catch (err: unknown) {
if (options?.force && err instanceof Deno.errors.NotFound) {
return;
}
throw err;
if (err instanceof Error) {
throw denoErrorToNodeError(err, { syscall: "stat" });
} else {
throw err;
}
}
}
4 changes: 2 additions & 2 deletions node/_fs/_fs_rm_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Deno.test({
async fn() {
const dir = Deno.makeTempDirSync();
await new Promise<void>((resolve, reject) => {
rm(dir, (err) => {
rm(dir, { recursive: true }, (err) => {
if (err) reject(err);
resolve();
});
Expand Down Expand Up @@ -108,7 +108,7 @@ Deno.test({
name: "SYNC: removing empty folder",
fn() {
const dir = Deno.makeTempDirSync();
rmSync(dir);
rmSync(dir, { recursive: true });
assertEquals(existsSync(dir), false);
},
});
Expand Down
33 changes: 26 additions & 7 deletions node/_fs/_fs_stat.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { denoErrorToNodeError } from "../_errors.ts";

export type statOptions = {
bigint: boolean;
throwIfNoEntry?: boolean;
};

export type Stats = {
Expand Down Expand Up @@ -270,20 +273,36 @@ export function stat(

Deno.stat(path).then(
(stat) => callback(null, CFISBIS(stat, options.bigint)),
(err) => callback(err),
(err) => callback(denoErrorToNodeError(err, { syscall: "stat" })),
);
}

export function statSync(path: string | URL): Stats;
export function statSync(path: string | URL, options: { bigint: false }): Stats;
export function statSync(
path: string | URL,
options: { bigint: true },
options: { bigint: false; throwIfNoEntry?: boolean },
): Stats;
export function statSync(
path: string | URL,
options: { bigint: true; throwIfNoEntry?: boolean },
): BigIntStats;
export function statSync(
path: string | URL,
options: statOptions = { bigint: false },
): Stats | BigIntStats {
const origin = Deno.statSync(path);
return CFISBIS(origin, options.bigint);
options: statOptions = { bigint: false, throwIfNoEntry: true },
): Stats | BigIntStats | undefined {
try {
const origin = Deno.statSync(path);
return CFISBIS(origin, options.bigint);
} catch (err: unknown) {
if (
options?.throwIfNoEntry === false && err instanceof Deno.errors.NotFound
) {
return;
}
if (err instanceof Error) {
throw denoErrorToNodeError(err, { syscall: "stat" });
} else {
throw err;
}
}
}
31 changes: 3 additions & 28 deletions node/_fs/_fs_writeFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
WriteFileOptions,
} from "./_fs_common.ts";
import { isWindows } from "../../_util/os.ts";
import { AbortError, uvException } from "../_errors.ts";
import { AbortError, denoErrorToNodeError } from "../_errors.ts";

export function writeFile(
pathOrRid: string | number | URL,
Expand Down Expand Up @@ -65,7 +65,7 @@ export function writeFile(
await writeAll(file, data as Uint8Array, { signal });
} catch (e) {
error = e instanceof Error
? convertDenoErrorToNodeError(e)
? denoErrorToNodeError(e, { syscall: "write" })
: new Error("[non-error thrown]");
} finally {
// Make sure to close resource
Expand Down Expand Up @@ -112,7 +112,7 @@ export function writeFileSync(
writeAllSync(file, data as Uint8Array);
} catch (e) {
error = e instanceof Error
? convertDenoErrorToNodeError(e)
? denoErrorToNodeError(e, { syscall: "write" })
: new Error("[non-error thrown]");
} finally {
// Make sure to close resource
Expand Down Expand Up @@ -153,28 +153,3 @@ function checkAborted(signal?: AbortSignal) {
throw new AbortError();
}
}

function convertDenoErrorToNodeError(e: Error) {
const errno = extractOsErrorNumberFromErrorMessage(e);
if (typeof errno === "undefined") {
return e;
}

const ex = uvException({
errno: -errno,
syscall: "writeFile",
});
return ex;
}

function extractOsErrorNumberFromErrorMessage(e: unknown): number | undefined {
const match = e instanceof Error
? e.message.match(/\(os error (\d+)\)/)
: false;

if (match) {
return +match[1];
}

return undefined;
}
1 change: 1 addition & 0 deletions node/_tools/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"test-event-emitter-symbols.js",
"test-events-list.js",
"test-events-once.js",
"test-fs-rm.js",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice 👍

"test-fs-write-file.js",
"test-next-tick-doesnt-hang.js",
"test-next-tick-fixed-queue-regression.js",
Expand Down
Loading