From 24f41f67bdbc9f426e3f9f03598a1010748d8200 Mon Sep 17 00:00:00 2001 From: Axetroy Date: Sun, 7 Apr 2019 09:01:23 +0800 Subject: [PATCH] fix: ensure exists file/dir must be the same type or it will throw error (#294) --- fs/ensure_dir.ts | 30 +++++++++++++++++++++++++----- fs/ensure_dir_test.ts | 40 ++++++++++++++++++++++++++++++++++++---- fs/ensure_file.ts | 29 +++++++++++++++++++++++++---- fs/ensure_file_test.ts | 32 ++++++++++++++++++++++++++++++++ fs/utils.ts | 17 +++++++++++++++++ fs/utils_test.ts | 35 ++++++++++++++++++++++++++++++++++- 6 files changed, 169 insertions(+), 14 deletions(-) diff --git a/fs/ensure_dir.ts b/fs/ensure_dir.ts index e7e4f69a20..dfc02f35c3 100644 --- a/fs/ensure_dir.ts +++ b/fs/ensure_dir.ts @@ -1,14 +1,24 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - +import { getFileInfoType } from "./utils.ts"; /** * Ensures that the directory exists. * If the directory structure does not exist, it is created. Like mkdir -p. */ export async function ensureDir(dir: string): Promise { + let pathExists = false; try { // if dir exists - await Deno.stat(dir); - } catch { + const stat = await Deno.stat(dir); + pathExists = true; + if (!stat.isDirectory()) { + throw new Error( + `Ensure path exists, expected 'dir', got '${getFileInfoType(stat)}'` + ); + } + } catch (err) { + if (pathExists) { + throw err; + } // if dir not exists. then create it. await Deno.mkdir(dir, true); } @@ -19,10 +29,20 @@ export async function ensureDir(dir: string): Promise { * If the directory structure does not exist, it is created. Like mkdir -p. */ export function ensureDirSync(dir: string): void { + let pathExists = false; try { // if dir exists - Deno.statSync(dir); - } catch { + const stat = Deno.statSync(dir); + pathExists = true; + if (!stat.isDirectory()) { + throw new Error( + `Ensure path exists, expected 'dir', got '${getFileInfoType(stat)}'` + ); + } + } catch (err) { + if (pathExists) { + throw err; + } // if dir not exists. then create it. Deno.mkdirSync(dir, true); } diff --git a/fs/ensure_dir_test.ts b/fs/ensure_dir_test.ts index 2e7936ae9c..3426588b7d 100644 --- a/fs/ensure_dir_test.ts +++ b/fs/ensure_dir_test.ts @@ -3,6 +3,7 @@ import { test } from "../testing/mod.ts"; import { assertThrows, assertThrowsAsync } from "../testing/asserts.ts"; import { ensureDir, ensureDirSync } from "./ensure_dir.ts"; import * as path from "./path/mod.ts"; +import { ensureFile, ensureFileSync } from "./ensure_file.ts"; const testdataDir = path.resolve("fs", "testdata"); @@ -27,10 +28,7 @@ test(function ensureDirSyncIfItNotExist() { ensureDirSync(testDir); - assertThrows(() => { - Deno.statSync(testDir); - throw new Error("test dir should exists."); - }); + Deno.statSync(testDir); Deno.removeSync(baseDir, { recursive: true }); }); @@ -69,3 +67,37 @@ test(function ensureDirSyncIfItExist() { Deno.removeSync(baseDir, { recursive: true }); }); + +test(async function ensureDirIfItAsFile() { + const baseDir = path.join(testdataDir, "ensure_dir_exist_file"); + const testFile = path.join(baseDir, "test"); + + await ensureFile(testFile); + + await assertThrowsAsync( + async () => { + await ensureDir(testFile); + }, + Error, + `Ensure path exists, expected 'dir', got 'file'` + ); + + await Deno.remove(baseDir, { recursive: true }); +}); + +test(function ensureDirSyncIfItAsFile() { + const baseDir = path.join(testdataDir, "ensure_dir_exist_file_async"); + const testFile = path.join(baseDir, "test"); + + ensureFileSync(testFile); + + assertThrows( + () => { + ensureDirSync(testFile); + }, + Error, + `Ensure path exists, expected 'dir', got 'file'` + ); + + Deno.removeSync(baseDir, { recursive: true }); +}); diff --git a/fs/ensure_file.ts b/fs/ensure_file.ts index 56632f1502..6fe8e78223 100644 --- a/fs/ensure_file.ts +++ b/fs/ensure_file.ts @@ -1,6 +1,7 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import * as path from "./path/mod.ts"; import { ensureDir, ensureDirSync } from "./ensure_dir.ts"; +import { getFileInfoType } from "./utils.ts"; /** * Ensures that the file exists. @@ -8,10 +9,20 @@ import { ensureDir, ensureDirSync } from "./ensure_dir.ts"; * these directories are created. If the file already exists, it is NOT MODIFIED. */ export async function ensureFile(filePath: string): Promise { + let pathExists = false; try { // if file exists - await Deno.stat(filePath); - } catch { + const stat = await Deno.lstat(filePath); + pathExists = true; + if (!stat.isFile()) { + throw new Error( + `Ensure path exists, expected 'file', got '${getFileInfoType(stat)}'` + ); + } + } catch (err) { + if (pathExists) { + throw err; + } // if file not exists // ensure dir exists await ensureDir(path.dirname(filePath)); @@ -26,10 +37,20 @@ export async function ensureFile(filePath: string): Promise { * these directories are created. If the file already exists, it is NOT MODIFIED. */ export function ensureFileSync(filePath: string): void { + let pathExists = false; try { // if file exists - Deno.statSync(filePath); - } catch { + const stat = Deno.statSync(filePath); + pathExists = true; + if (!stat.isFile()) { + throw new Error( + `Ensure path exists, expected 'file', got '${getFileInfoType(stat)}'` + ); + } + } catch (err) { + if (pathExists) { + throw err; + } // if file not exists // ensure dir exists ensureDirSync(path.dirname(filePath)); diff --git a/fs/ensure_file_test.ts b/fs/ensure_file_test.ts index 2199e36058..fd3f4718ad 100644 --- a/fs/ensure_file_test.ts +++ b/fs/ensure_file_test.ts @@ -69,3 +69,35 @@ test(function ensureFileSyncIfItExist() { Deno.removeSync(testDir, { recursive: true }); }); + +test(async function ensureFileIfItExistAsDir() { + const testDir = path.join(testdataDir, "ensure_file_5"); + + await Deno.mkdir(testDir, true); + + await assertThrowsAsync( + async () => { + await ensureFile(testDir); + }, + Error, + `Ensure path exists, expected 'file', got 'dir'` + ); + + await Deno.remove(testDir, { recursive: true }); +}); + +test(function ensureFileSyncIfItExistAsDir() { + const testDir = path.join(testdataDir, "ensure_file_6"); + + Deno.mkdirSync(testDir, true); + + assertThrows( + () => { + ensureFileSync(testDir); + }, + Error, + `Ensure path exists, expected 'file', got 'dir'` + ); + + Deno.removeSync(testDir, { recursive: true }); +}); diff --git a/fs/utils.ts b/fs/utils.ts index dd7ee5fa40..410e459094 100644 --- a/fs/utils.ts +++ b/fs/utils.ts @@ -21,3 +21,20 @@ export function isSubdir( return acc && destArray[i] === current; }, true); } + +export enum PathType { + file = "file", + dir = "dir", + symlink = "symlink" +} + +/* Get a human readable file type string */ +export function getFileInfoType(fileInfo: Deno.FileInfo): PathType | null { + return fileInfo.isFile() + ? PathType.file + : fileInfo.isDirectory() + ? PathType.dir + : fileInfo.isSymlink() + ? PathType.symlink + : null; +} diff --git a/fs/utils_test.ts b/fs/utils_test.ts index 9d6959de5f..9f8d10cb01 100644 --- a/fs/utils_test.ts +++ b/fs/utils_test.ts @@ -2,8 +2,12 @@ import { test } from "../testing/mod.ts"; import { assertEquals } from "../testing/asserts.ts"; -import { isSubdir } from "./utils.ts"; +import { isSubdir, getFileInfoType, PathType } from "./utils.ts"; import * as path from "./path/mod.ts"; +import { ensureFileSync } from "./ensure_file.ts"; +import { ensureDirSync } from "./ensure_dir.ts"; + +const testdataDir = path.resolve("fs", "testdata"); test(function _isSubdir() { const pairs = [ @@ -29,3 +33,32 @@ test(function _isSubdir() { ); }); }); + +test(function _getFileInfoType() { + const pairs = [ + [path.join(testdataDir, "file_type_1"), PathType.file], + [path.join(testdataDir, "file_type_dir_1"), PathType.dir] + ]; + + pairs.forEach(function(p) { + const filePath = p[0] as string; + const type = p[1] as PathType; + switch (type) { + case PathType.file: + ensureFileSync(filePath); + break; + case PathType.dir: + ensureDirSync(filePath); + break; + case PathType.symlink: + // TODO(axetroy): test symlink + break; + } + + const stat = Deno.statSync(filePath); + + Deno.removeSync(filePath, { recursive: true }); + + assertEquals(getFileInfoType(stat), type); + }); +});