diff --git a/.changeset/deno-write-copy-errors.md b/.changeset/deno-write-copy-errors.md new file mode 100644 index 00000000000..2b93ed38e63 --- /dev/null +++ b/.changeset/deno-write-copy-errors.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Preserve high-level filesystem error context for `writeFile` and normalize Deno `AlreadyExists` errors from `copy`. diff --git a/packages/effect/test/FileSystem.test-utils.ts b/packages/effect/test/FileSystem.test-utils.ts index a89dfb39ba5..3490d7db974 100644 --- a/packages/effect/test/FileSystem.test-utils.ts +++ b/packages/effect/test/FileSystem.test-utils.ts @@ -1,5 +1,5 @@ import { assert, expect, it } from "@effect/vitest" -import { Array } from "effect" +import { Array, Result } from "effect" import * as Effect from "effect/Effect" import * as Fs from "effect/FileSystem" import type * as Layer from "effect/Layer" @@ -137,8 +137,11 @@ export const testLayer = (layer: Layer.Layer, options: Test const fs = yield* Fs.FileSystem const path = yield* fs.makeTempFile() - yield* fs.writeFileString(path, "data", { flag: "r" }).pipe(Effect.flip) + const error = yield* fs.writeFileString(path, "data", { flag: "r" }).pipe(Effect.flip) + assert(error.reason._tag !== "BadArgument") + assert.strictEqual(error.reason.method, "writeFile") + assert.strictEqual(error.reason.pathOrDescriptor, path) assert.strictEqual(yield* fs.readFileString(path), "") }))) @@ -165,6 +168,26 @@ export const testLayer = (layer: Layer.Layer, options: Test assert.strictEqual(yield* fs.readFileString(path), "first") }))) + it("copy with overwrite false preserves an existing destination", () => + runPromise(Effect.gen(function*() { + const fs = yield* Fs.FileSystem + const root = yield* fs.makeTempDirectory() + const source = `${root}/source.txt` + const destination = `${root}/destination.txt` + yield* fs.writeFileString(source, "source") + yield* fs.writeFileString(destination, "destination") + + const result = yield* Effect.result(fs.copy(source, destination, { overwrite: false })) + + if (Result.isFailure(result)) { + assert(result.failure.reason._tag === "AlreadyExists") + assert.strictEqual(result.failure.reason.method, "copy") + assert.strictEqual(result.failure.reason.pathOrDescriptor, source) + } + assert.strictEqual(yield* fs.readFileString(source), "source") + assert.strictEqual(yield* fs.readFileString(destination), "destination") + }))) + it("should track the cursor position when reading", () => runPromise(Effect.gen(function*() { const fs = yield* Fs.FileSystem diff --git a/packages/platform-deno/src/DenoFileSystem.ts b/packages/platform-deno/src/DenoFileSystem.ts index bf8a4188ce0..9e263a7c357 100644 --- a/packages/platform-deno/src/DenoFileSystem.ts +++ b/packages/platform-deno/src/DenoFileSystem.ts @@ -452,7 +452,14 @@ const writeFile: FileSystem.FileSystem["writeFile"] = (path, data, options) => { } return Effect.acquireUseRelease( tryPromise("writeFile", path, () => Deno.open(path, openOptions(flag, options?.mode))), - (file) => new FileImpl(file, flag.startsWith("a")).writeAll(data), + (file) => + new FileImpl(file, flag.startsWith("a")).writeAll(data).pipe( + Effect.mapError((error) => + error.reason._tag !== "BadArgument" + ? PlatformError.systemError({ ...error.reason, method: "writeFile", pathOrDescriptor: path }) + : error + ) + ), (file) => close(file, "writeFile", path) ) } diff --git a/packages/platform-deno/src/internal/error.ts b/packages/platform-deno/src/internal/error.ts index 97c73ea055f..11a0f5a0a8e 100644 --- a/packages/platform-deno/src/internal/error.ts +++ b/packages/platform-deno/src/internal/error.ts @@ -16,6 +16,9 @@ export const handleError = ( let tag: SystemErrorTag = "Unknown" switch (denoError?.name) { + case "AlreadyExists": + tag = "AlreadyExists" + break case "NotCapable": tag = "PermissionDenied" break diff --git a/packages/platform-deno/test/internal/error.test.ts b/packages/platform-deno/test/internal/error.test.ts index a31f54170e6..991974ed999 100644 --- a/packages/platform-deno/test/internal/error.test.ts +++ b/packages/platform-deno/test/internal/error.test.ts @@ -9,6 +9,7 @@ describe("handleError", () => { const cases: ReadonlyArray = [ [withCode(new Deno.errors.NotFound(), "ENOENT"), "NotFound"], [withCode(new Deno.errors.NotADirectory(), "ENOTDIR"), "BadResource"], + [new Deno.errors.AlreadyExists(), "AlreadyExists"], [withCode(new Deno.errors.AlreadyExists(), "EEXIST"), "AlreadyExists"], [withCode(new Deno.errors.IsADirectory(), "EISDIR"), "BadResource"], [withCode(new Deno.errors.PermissionDenied(), "EACCES"), "PermissionDenied"],