diff --git a/src/utils/io/file-path.ts b/src/utils/io/file-path.ts new file mode 100644 index 0000000..495eb8b --- /dev/null +++ b/src/utils/io/file-path.ts @@ -0,0 +1,67 @@ +import { PathLike } from "node:fs"; +import { FileHandle } from "node:fs/promises"; + +/** + * Represents a file path for asynchronous (non-blocking) operations. + */ +export type AsyncFilePath = PathLike | FileHandle; + +/** + * Represents a file path for synchronous (blocking) operations. + */ +export type SyncFilePath = PathLike | number; + +/** + * Represents a file path. + */ +export type FilePath = AsyncFilePath | SyncFilePath; + +/** + * Determines if the provided value is a valid {@link FilePath}. + * + * @param path - The value to check. + * + * @returns `true` if the value is a valid {@link FilePath}; otherwise, `false`. + */ +export function isFilePath(path: unknown): path is FilePath { + return ( + typeof path === "string" || + typeof path === "number" || + typeof (path as URL)?.pathname === "string" || + Buffer.isBuffer(path) || + isFileHandle(path) + ); +} + +/** + * Determines if the provided value is a valid {@link AsyncFilePath}. + * + * @param path - The value to check. + * + * @returns `true` if the value is a valid {@link AsyncFilePath}; otherwise, `false`. + */ +export function isAsyncFilePath(path: unknown): path is AsyncFilePath { + return isFilePath(path) && typeof path !== "number"; +} + +/** + * Determines if the provided value is a valid {@link SyncFilePath}. + * + * @param path - The value to check. + * + * @returns `true` if the value is a valid {@link SyncFilePath}; otherwise, `false`. + */ +export function isSyncFilePath(path: unknown): path is SyncFilePath { + return isFilePath(path) && !isFileHandle(path); +} + +/** + * Determines if the provided value is a valid {@link FileHandle}. + * + * @param path - The value to check. + * + * @returns `true` if the value is a valid {@link FileHandle}; otherwise, `false`. + */ +function isFileHandle(handle: unknown): handle is FileHandle { + return typeof (handle as FileHandle)?.fd === "number"; +} diff --git a/tests/unit/utils/io/file-path.spec.ts b/tests/unit/utils/io/file-path.spec.ts new file mode 100644 index 0000000..a46b096 --- /dev/null +++ b/tests/unit/utils/io/file-path.spec.ts @@ -0,0 +1,86 @@ +import { isAsyncFilePath, isFilePath, isSyncFilePath } from "@/utils/io/file-path"; +import { FileHandle } from "node:fs/promises"; + +describe("isFilePath", () => { + test("returns true for string file paths", () => { + expect(isFilePath("path/to/file.txt")).toBe(true); + }); + + test("returns true for file descriptors", () => { + expect(isFilePath(123)).toBe(true); + }); + + test("returns true for URL file paths", () => { + expect(isFilePath(new URL("file:///path/to/file.txt"))).toBe(true); + }); + + test("returns true for Buffer file paths", () => { + expect(isFilePath(Buffer.from("path/to/file.txt"))).toBe(true); + }); + + test("returns true for file handles", () => { + expect(isFilePath({ fd: 1 } as FileHandle)).toBe(true); + }); + + test("returns false for invalid file paths", () => { + expect(isFilePath(null)).toBe(false); + expect(isFilePath(undefined)).toBe(false); + expect(isFilePath({})).toBe(false); + expect(isFilePath([])).toBe(false); + }); +}); + +describe("isAsyncFilePath", () => { + test("returns true for string file paths", () => { + expect(isAsyncFilePath("path/to/file.txt")).toBe(true); + }); + + test("returns true for URL file paths", () => { + expect(isAsyncFilePath(new URL("file:///path/to/file.txt"))).toBe(true); + }); + + test("returns true for Buffer file paths", () => { + expect(isAsyncFilePath(Buffer.from("path/to/file.txt"))).toBe(true); + }); + + test("returns true for file handles", () => { + expect(isAsyncFilePath({ fd: 1 } as FileHandle)).toBe(true); + }); + + test("returns false for invalid file paths", () => { + expect(isAsyncFilePath(null)).toBe(false); + expect(isAsyncFilePath(undefined)).toBe(false); + expect(isAsyncFilePath({})).toBe(false); + expect(isAsyncFilePath([])).toBe(false); + expect(isAsyncFilePath(1)).toBe(false); + }); +}); + +describe("isSyncFilePath", () => { + test("returns true for string file paths", () => { + expect(isSyncFilePath("path/to/file.txt")).toBe(true); + }); + + test("returns true for file descriptors", () => { + expect(isSyncFilePath(123)).toBe(true); + }); + + test("returns true for URL file paths", () => { + expect(isSyncFilePath(new URL("file:///path/to/file.txt"))).toBe(true); + }); + + test("returns true for Buffer file paths", () => { + expect(isSyncFilePath(Buffer.from("path/to/file.txt"))).toBe(true); + }); + + test("returns false for file handles", () => { + expect(isSyncFilePath({ fd: 1 } as FileHandle)).toBe(false); + }); + + test("returns false for invalid file paths", () => { + expect(isSyncFilePath(null)).toBe(false); + expect(isSyncFilePath(undefined)).toBe(false); + expect(isSyncFilePath({})).toBe(false); + expect(isSyncFilePath([])).toBe(false); + }); +});