Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compat(fs): opendir and opendirSync #2576

Merged
merged 15 commits into from
Sep 1, 2022
Merged
89 changes: 89 additions & 0 deletions node/_fs/_fs_opendir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.

import Dir from "./_fs_dir.ts";
import { Buffer } from "../buffer.ts";
import { assertEncoding, getValidatedPath } from "../internal/fs/utils.mjs";
import { denoErrorToNodeError } from "../internal/errors.ts";
import { validateFunction } from "../internal/validators.mjs";
import { promisify } from "../internal/util.mjs";

type Options = {
encoding?: string;
bufferSize?: number;
};
type Callback = (err?: Error | null, dir?: Dir) => void;

function _validateFunction(callback: unknown): asserts callback is Callback {
validateFunction(callback, "callback");
}

function checkBufferSize(value: number, name: string) {
if (!Number.isInteger(value) || value < 1 || value > 4294967295) {
throw new RangeError(
`The value of "${name}" is out of range. It must be >= 1 && <= 4294967295. Received ${value}`,
);
}
}

export function opendir(
path: string | Buffer | URL,
options: Options | Callback,
callback?: Callback,
) {
callback = typeof options === "function" ? options : callback;
_validateFunction(callback);

path = getValidatedPath(path).toString();

options = typeof options === "object" ? options : {};
options = Object.assign({
encoding: "utf8",
bufferSize: 32,
}, options);

try {
assertEncoding(options.encoding);

checkBufferSize(options.bufferSize!, "options.bufferSize");

/** Throws if path is invalid */
Deno.readDirSync(path);

callback(null, new Dir(path));
} catch (err) {
callback(denoErrorToNodeError(err as Error, { syscall: "opendir" }));
}
}

export const opendirPromise = promisify(opendir) as (
Copy link
Member

Choose a reason for hiding this comment

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

Nice to have the promise version 👍

path: string | Buffer | URL,
options?: Options,
) => Promise<Dir>;

export function opendirSync(
path: string | Buffer | URL,
options: Options = {
encoding: "utf8",
bufferSize: 32,
},
): Dir {
path = getValidatedPath(path).toString();

options = Object.assign({
encoding: "utf8",
bufferSize: 32,
}, options);

try {
assertEncoding(options.encoding);

checkBufferSize(options.bufferSize!, "options.bufferSize");

/** Throws if path is invalid */
Deno.readDirSync(path);

return new Dir(path);
} catch (err) {
throw denoErrorToNodeError(err as Error, { syscall: "opendir" });
}
}
135 changes: 135 additions & 0 deletions node/_fs/_fs_opendir_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.

import {
assert,
assertEquals,
assertFalse,
assertInstanceOf,
assertThrows,
} from "../../testing/asserts.ts";
import { opendir, opendirSync } from "./_fs_opendir.ts";
import { Buffer } from "../buffer.ts";

Deno.test("[node/fs] opendir()", async (t) => {
const path = await Deno.makeTempDir();
const file = await Deno.makeTempFile();

await t.step(
"fails if encoding is invalid",
() =>
opendir(
path,
{ encoding: "invalid-encoding" },
(err) => assertInstanceOf(err, TypeError),
),
);

await t.step(
"fails if bufferSize is invalid",
() =>
opendir(
path,
{ bufferSize: -1 },
(err) => assertInstanceOf(err, RangeError),
),
);

await t.step(
"fails if directory does not exist",
() =>
opendir(
"directory-that-does-not-exist",
(err) => assertInstanceOf(err, Error),
),
);

await t.step(
"fails if not a directory",
() =>
opendir(
file,
(err) => assertInstanceOf(err, Error),
),
);

await t.step(
"passes if path is a string",
() =>
opendir(
path,
(err, dir) => {
assertEquals(err, null);
assert(dir);
},
),
);

await t.step(
"passes if path is a Buffer",
() =>
opendir(
Buffer.from(path),
(err, dir) => {
assertFalse(err);
assert(dir);
},
),
);

await t.step(
"passes if path is a URL",
() =>
opendir(
new URL(`file://` + path),
(err, dir) => {
assertFalse(err);
assert(dir);
},
),
);

await Deno.remove(path);
await Deno.remove(file);
});

Deno.test("[node/fs] opendirSync()", async (t) => {
const path = await Deno.makeTempDir();
const file = await Deno.makeTempFile();

await t.step("fails if encoding is invalid", () => {
assertThrows(
() => opendirSync(path, { encoding: "invalid-encoding" }),
TypeError,
);
});

await t.step("fails if bufferSize is invalid", () => {
assertThrows(
() => opendirSync(path, { bufferSize: -1 }),
RangeError,
);
});

await t.step("fails if directory does not exist", () => {
assertThrows(() => opendirSync("directory-that-does-not-exist"));
});

await t.step("fails if not a directory", () => {
assertThrows(() => opendirSync(file));
});

await t.step("passes if path is a string", () => {
assert(opendirSync(path));
});

await t.step("passes if path is a Buffer", () => {
assert(opendirSync(Buffer.from(path)));
});

await t.step("passes if path is a URL", () => {
assert(opendirSync(new URL(`file://` + path)));
});

await Deno.remove(path);
await Deno.remove(file);
});
1 change: 1 addition & 0 deletions node/_tools/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"test-fs-append-file.js",
"test-fs-chmod-mask.js",
"test-fs-chmod.js",
"test-fs-opendir.js",
Copy link
Member

Choose a reason for hiding this comment

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

Great to have this enabled! 👍

"test-fs-rmdir-recursive.js",
"test-fs-write-file.js",
"test-fs-write.js",
Expand Down
Loading