From 2de258569b4faee3746bc042285f6528a5301e87 Mon Sep 17 00:00:00 2001 From: Felix Green Date: Wed, 24 Dec 2025 09:10:06 -0500 Subject: [PATCH 01/10] add support for error ehostdown --- CHANGELOG.md | 1 + src/simplifile.gleam | 3 +++ src/simplifile_erl.erl | 1 + src/simplifile_js.mjs | 2 ++ test/simplifile_test.gleam | 4 +++- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d49b3c..3912650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Add documentation for 'mode' field in FileInfo. ## Unreleased +- Add support for POSIX error `Ehostdown`. ## v2.3.1 - 6 November 2025 - Fix bug with error string typo for `Enotdir` on JS. diff --git a/src/simplifile.gleam b/src/simplifile.gleam index b38c40c..4659b04 100644 --- a/src/simplifile.gleam +++ b/src/simplifile.gleam @@ -39,6 +39,8 @@ pub type FileError { /// Inappropriate file type or format. Usually caused by trying to set the /// "sticky bit" on a regular file (not a directory). Eftype + /// Host is down. + Ehostdown /// Interrupted system call. Eintr /// Invalid argument. @@ -170,6 +172,7 @@ pub fn describe_error(error: FileError) -> String { Enosr -> "No STREAM resources" Enostr -> "Not a STREAM" Eopnotsupp -> "Operation not supported on socket" + Ehostdown -> "Host is down" NotUtf8 -> "File not UTF-8 encoded" Unknown(inner) -> "Unknown error: " <> inner } diff --git a/src/simplifile_erl.erl b/src/simplifile_erl.erl index 09bef7a..6b714ca 100644 --- a/src/simplifile_erl.erl +++ b/src/simplifile_erl.erl @@ -46,6 +46,7 @@ orelse Error =:= efault orelse Error =:= efbig orelse Error =:= eftype + orelse Error =:= ehostdown orelse Error =:= eintr orelse Error =:= einval orelse Error =:= eio diff --git a/src/simplifile_js.mjs b/src/simplifile_js.mjs index 1c003df..d8b31e2 100644 --- a/src/simplifile_js.mjs +++ b/src/simplifile_js.mjs @@ -324,6 +324,8 @@ function cast_error(error_code) { return new $simplifile.Eftype(); case "EINTR": return new $simplifile.Eintr(); + case "EHOSTDOWN": + return new $simplifile.Ehostdown(); case "EINVAL": return new $simplifile.Einval(); case "EIO": diff --git a/test/simplifile_test.gleam b/test/simplifile_test.gleam index 48701c6..5195f8c 100644 --- a/test/simplifile_test.gleam +++ b/test/simplifile_test.gleam @@ -5,7 +5,7 @@ import gleeunit import gleeunit/should import simplifile.{ Directory, Eacces, Eagain, Ebadf, Ebadmsg, Ebusy, Edeadlk, Edeadlock, Edquot, - Eexist, Efault, Efbig, Eftype, Einval, Eio, Eisdir, Eloop, Emfile, Emlink, + Eexist, Efault, Efbig, Eftype, Ehostdown, Einval, Eio, Eisdir, Eloop, Emfile, Emlink, Emultihop, Enametoolong, Enfile, Enobufs, Enodev, Enoent, Enolck, Enolink, Enomem, Enospc, Enosr, Enostr, Enosys, Enotblk, Enotdir, Enotsup, Enxio, Eopnotsupp, Eoverflow, Eperm, Epipe, Erange, Erofs, Espipe, Esrch, Estale, @@ -692,6 +692,8 @@ pub fn describe_error_test() { let assert "Operation not supported on socket" = simplifile.describe_error(Eopnotsupp) + let assert "Host is down" = simplifile.describe_error(Ehostdown) + let assert "File not UTF-8 encoded" = simplifile.describe_error(NotUtf8) let assert "Unknown error: Something went wrong" = From 228f5c928093dbd065a6950b2f3873f32c34b90c Mon Sep 17 00:00:00 2001 From: Felix Green Date: Wed, 24 Dec 2025 09:48:41 -0500 Subject: [PATCH 02/10] handle unknown errors in erlang --- src/simplifile_erl.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/simplifile_erl.erl b/src/simplifile_erl.erl index 6b714ca..b2fdb12 100644 --- a/src/simplifile_erl.erl +++ b/src/simplifile_erl.erl @@ -92,9 +92,14 @@ posix_result(Result) -> {ok, Value} -> {ok, Value}; {error, Reason} when ?is_posix_error(Reason) -> - {error, Reason} + {error, Reason}; + {error, Reason} -> + {error, {unknown, error_reason_to_binary(Reason)}} end. +error_reason_to_binary(Reason) -> + unicode:characters_to_binary(io_lib:format("~tp", [Reason])). + %% Read the binary contents of a file read_bits(Filename) -> posix_result(file:read_file(Filename)). From 919bc9276a445fbc360d355311391783191bdc87 Mon Sep 17 00:00:00 2001 From: Felix Green Date: Wed, 24 Dec 2025 10:01:56 -0500 Subject: [PATCH 03/10] add test for unknown errors --- CHANGELOG.md | 1 + test/create_directory_bad_arg.mjs | 5 +++++ test/simplifile_test.gleam | 37 +++++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 test/create_directory_bad_arg.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3912650..0eb1ead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## Unreleased - Add support for POSIX error `Ehostdown`. +- Fix bug where unknown errrs were not handled in Erlang ffi. ## v2.3.1 - 6 November 2025 - Fix bug with error string typo for `Enotdir` on JS. diff --git a/test/create_directory_bad_arg.mjs b/test/create_directory_bad_arg.mjs new file mode 100644 index 0000000..7a096a3 --- /dev/null +++ b/test/create_directory_bad_arg.mjs @@ -0,0 +1,5 @@ +import { createDirectory } from "./simplifile_js.mjs"; + +export function createDirectoryWithBadArg(arg) { + return createDirectory(arg); +} diff --git a/test/simplifile_test.gleam b/test/simplifile_test.gleam index 5195f8c..91f5c53 100644 --- a/test/simplifile_test.gleam +++ b/test/simplifile_test.gleam @@ -1,18 +1,19 @@ import gleam/int import gleam/list import gleam/set +import gleam/string import gleeunit import gleeunit/should -import simplifile.{ +import simplifile.{type FileError, Directory, Eacces, Eagain, Ebadf, Ebadmsg, Ebusy, Edeadlk, Edeadlock, Edquot, - Eexist, Efault, Efbig, Eftype, Ehostdown, Einval, Eio, Eisdir, Eloop, Emfile, Emlink, - Emultihop, Enametoolong, Enfile, Enobufs, Enodev, Enoent, Enolck, Enolink, - Enomem, Enospc, Enosr, Enostr, Enosys, Enotblk, Enotdir, Enotsup, Enxio, + Eexist, Efault, Efbig, Eftype, Ehostdown, Einval, Eio, Eisdir, Eloop, Emfile, + Emlink, Emultihop, Enametoolong, Enfile, Enobufs, Enodev, Enoent, Enolck, + Enolink, Enomem, Enospc, Enosr, Enostr, Enosys, Enotblk, Enotdir, Enotsup, Enxio, Eopnotsupp, Eoverflow, Eperm, Epipe, Erange, Erofs, Espipe, Esrch, Estale, - Etxtbsy, Exdev, Execute, File, FilePermissions, NotUtf8, Read, Unknown, Write, - append, append_bits, copy, copy_directory, copy_file, create_directory, - create_directory_all, create_file, create_link, create_symlink, delete, - delete_all, file_info, file_info_permissions, file_info_permissions_octal, + Etxtbsy, Exdev, Execute, File, FilePermissions, NotUtf8, Read, + Unknown, Write, append, append_bits, copy, copy_directory, copy_file, + create_directory, create_directory_all, create_file, create_link, create_symlink, + delete, delete_all, file_info, file_info_permissions, file_info_permissions_octal, file_info_type, file_permissions_to_octal, get_files, is_directory, is_file, is_symlink, link_info, read, read_bits, read_directory, rename, set_permissions, set_permissions_octal, write, write_bits, @@ -483,7 +484,11 @@ pub fn file_info_get_permissions_test() { pub fn get_files_with_slash_test() { let assert Ok(files) = get_files(in: "./test/") files - |> should.equal(["./test/simplifile_test.gleam"]) + |> list.sort(string.compare) + |> should.equal([ + "./test/create_directory_bad_arg.mjs", + "./test/simplifile_test.gleam", + ]) } // This test is only for local development @@ -763,3 +768,17 @@ pub fn parse_errors_test() { simplifile.create_directory_all("./tmp/wumbo/wombo") |> should.be_error err |> should.equal(Enotdir) } + +pub fn unknown_errors_return_unknown_test() { + let err = create_directory_with_bad_arg(#(Nil, Nil)) + err |> should.be_error + + let assert Error(unknown) = err + let assert Unknown(inner) = unknown + inner |> should.not_equal("") +} + +// This is necessary to force unknown error generation uniformly across runtimes +@external(erlang, "simplifile_erl", "create_directory") +@external(javascript, "./create_directory_bad_arg.mjs", "createDirectoryWithBadArg") +fn create_directory_with_bad_arg(arg: #(Nil, Nil)) -> Result(Nil, FileError) From 349910505bf1f158ed8d89d259c46fe78e06a2cb Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt Date: Fri, 26 Dec 2025 15:07:02 -0600 Subject: [PATCH 04/10] simplify PR to fix just the crash first --- CHANGELOG.md | 5 +++-- gleam.toml | 2 +- src/simplifile.gleam | 3 --- src/simplifile_erl.erl | 6 +---- test/create_directory_bad_arg.mjs | 5 ----- test/simplifile_test.gleam | 37 ++++++++++++++----------------- 6 files changed, 22 insertions(+), 36 deletions(-) delete mode 100644 test/create_directory_bad_arg.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eb1ead..1ddac4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ - Add documentation for 'mode' field in FileInfo. ## Unreleased -- Add support for POSIX error `Ehostdown`. -- Fix bug where unknown errrs were not handled in Erlang ffi. + +## v2.3.2 - 26 December 2025 +- Fix bug where unknown errors were not properly converted to the Unknown vairant in Erlang ffi. ## v2.3.1 - 6 November 2025 - Fix bug with error string typo for `Enotdir` on JS. diff --git a/gleam.toml b/gleam.toml index 33a959a..b974558 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "simplifile" -version = "2.3.1" +version = "2.3.2" description = "Basic file operations that work on all targets" licences = ["Apache-2.0"] diff --git a/src/simplifile.gleam b/src/simplifile.gleam index 4659b04..b38c40c 100644 --- a/src/simplifile.gleam +++ b/src/simplifile.gleam @@ -39,8 +39,6 @@ pub type FileError { /// Inappropriate file type or format. Usually caused by trying to set the /// "sticky bit" on a regular file (not a directory). Eftype - /// Host is down. - Ehostdown /// Interrupted system call. Eintr /// Invalid argument. @@ -172,7 +170,6 @@ pub fn describe_error(error: FileError) -> String { Enosr -> "No STREAM resources" Enostr -> "Not a STREAM" Eopnotsupp -> "Operation not supported on socket" - Ehostdown -> "Host is down" NotUtf8 -> "File not UTF-8 encoded" Unknown(inner) -> "Unknown error: " <> inner } diff --git a/src/simplifile_erl.erl b/src/simplifile_erl.erl index b2fdb12..a1d138f 100644 --- a/src/simplifile_erl.erl +++ b/src/simplifile_erl.erl @@ -46,7 +46,6 @@ orelse Error =:= efault orelse Error =:= efbig orelse Error =:= eftype - orelse Error =:= ehostdown orelse Error =:= eintr orelse Error =:= einval orelse Error =:= eio @@ -94,12 +93,9 @@ posix_result(Result) -> {error, Reason} when ?is_posix_error(Reason) -> {error, Reason}; {error, Reason} -> - {error, {unknown, error_reason_to_binary(Reason)}} + {error, {unknown, string:uppercase(atom_to_binary(Reason))}} end. -error_reason_to_binary(Reason) -> - unicode:characters_to_binary(io_lib:format("~tp", [Reason])). - %% Read the binary contents of a file read_bits(Filename) -> posix_result(file:read_file(Filename)). diff --git a/test/create_directory_bad_arg.mjs b/test/create_directory_bad_arg.mjs deleted file mode 100644 index 7a096a3..0000000 --- a/test/create_directory_bad_arg.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import { createDirectory } from "./simplifile_js.mjs"; - -export function createDirectoryWithBadArg(arg) { - return createDirectory(arg); -} diff --git a/test/simplifile_test.gleam b/test/simplifile_test.gleam index 91f5c53..b40ca53 100644 --- a/test/simplifile_test.gleam +++ b/test/simplifile_test.gleam @@ -1,22 +1,22 @@ import gleam/int import gleam/list import gleam/set -import gleam/string import gleeunit import gleeunit/should -import simplifile.{type FileError, - Directory, Eacces, Eagain, Ebadf, Ebadmsg, Ebusy, Edeadlk, Edeadlock, Edquot, - Eexist, Efault, Efbig, Eftype, Ehostdown, Einval, Eio, Eisdir, Eloop, Emfile, - Emlink, Emultihop, Enametoolong, Enfile, Enobufs, Enodev, Enoent, Enolck, - Enolink, Enomem, Enospc, Enosr, Enostr, Enosys, Enotblk, Enotdir, Enotsup, Enxio, - Eopnotsupp, Eoverflow, Eperm, Epipe, Erange, Erofs, Espipe, Esrch, Estale, - Etxtbsy, Exdev, Execute, File, FilePermissions, NotUtf8, Read, +import simplifile.{ + type FileError, Directory, Eacces, Eagain, Ebadf, Ebadmsg, Ebusy, Edeadlk, + Edeadlock, Edquot, Eexist, Efault, Efbig, Eftype, Einval, Eio, Eisdir, Eloop, + Emfile, Emlink, Emultihop, Enametoolong, Enfile, Enobufs, Enodev, Enoent, + Enolck, Enolink, Enomem, Enospc, Enosr, Enostr, Enosys, Enotblk, Enotdir, + Enotsup, Enxio, Eopnotsupp, Eoverflow, Eperm, Epipe, Erange, Erofs, Espipe, + Esrch, Estale, Etxtbsy, Exdev, Execute, File, FilePermissions, NotUtf8, Read, Unknown, Write, append, append_bits, copy, copy_directory, copy_file, - create_directory, create_directory_all, create_file, create_link, create_symlink, - delete, delete_all, file_info, file_info_permissions, file_info_permissions_octal, - file_info_type, file_permissions_to_octal, get_files, is_directory, is_file, - is_symlink, link_info, read, read_bits, read_directory, rename, - set_permissions, set_permissions_octal, write, write_bits, + create_directory, create_directory_all, create_file, create_link, + create_symlink, delete, delete_all, file_info, file_info_permissions, + file_info_permissions_octal, file_info_type, file_permissions_to_octal, + get_files, is_directory, is_file, is_symlink, link_info, read, read_bits, + read_directory, rename, set_permissions, set_permissions_octal, write, + write_bits, } pub fn main() { @@ -484,9 +484,7 @@ pub fn file_info_get_permissions_test() { pub fn get_files_with_slash_test() { let assert Ok(files) = get_files(in: "./test/") files - |> list.sort(string.compare) |> should.equal([ - "./test/create_directory_bad_arg.mjs", "./test/simplifile_test.gleam", ]) } @@ -697,8 +695,6 @@ pub fn describe_error_test() { let assert "Operation not supported on socket" = simplifile.describe_error(Eopnotsupp) - let assert "Host is down" = simplifile.describe_error(Ehostdown) - let assert "File not UTF-8 encoded" = simplifile.describe_error(NotUtf8) let assert "Unknown error: Something went wrong" = @@ -770,15 +766,16 @@ pub fn parse_errors_test() { } pub fn unknown_errors_return_unknown_test() { - let err = create_directory_with_bad_arg(#(Nil, Nil)) + let err = create_directory_with_bad_arg(#(Nil, Nil)) err |> should.be_error let assert Error(unknown) = err let assert Unknown(inner) = unknown - inner |> should.not_equal("") + echo inner + inner |> should.not_equal("") } // This is necessary to force unknown error generation uniformly across runtimes @external(erlang, "simplifile_erl", "create_directory") -@external(javascript, "./create_directory_bad_arg.mjs", "createDirectoryWithBadArg") +@external(javascript, "./simplifile_js.mjs", "createDirectory") fn create_directory_with_bad_arg(arg: #(Nil, Nil)) -> Result(Nil, FileError) From 7b779036d33e74c198892603420e2dc4f08c8c8d Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt Date: Fri, 26 Dec 2025 15:10:46 -0600 Subject: [PATCH 05/10] remove unused js error handling --- src/simplifile_js.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/simplifile_js.mjs b/src/simplifile_js.mjs index d8b31e2..1c003df 100644 --- a/src/simplifile_js.mjs +++ b/src/simplifile_js.mjs @@ -324,8 +324,6 @@ function cast_error(error_code) { return new $simplifile.Eftype(); case "EINTR": return new $simplifile.Eintr(); - case "EHOSTDOWN": - return new $simplifile.Ehostdown(); case "EINVAL": return new $simplifile.Einval(); case "EIO": From 44836a53ad8e06574221400b71a956b7ab185455 Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt Date: Fri, 26 Dec 2025 15:13:30 -0600 Subject: [PATCH 06/10] remove docs entry from changelog as updated docs are published separately from versioning --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ddac4e..c412cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ # Changelog -- Add documentation for 'mode' field in FileInfo. - ## Unreleased ## v2.3.2 - 26 December 2025 From 796538187cd35154a844973c30430d3713a6204b Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt Date: Fri, 26 Dec 2025 15:16:07 -0600 Subject: [PATCH 07/10] update test to confirm error msg was uppercased --- test/simplifile_test.gleam | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/simplifile_test.gleam b/test/simplifile_test.gleam index b40ca53..872f5a2 100644 --- a/test/simplifile_test.gleam +++ b/test/simplifile_test.gleam @@ -1,6 +1,7 @@ import gleam/int import gleam/list import gleam/set +import gleam/string import gleeunit import gleeunit/should import simplifile.{ @@ -773,6 +774,8 @@ pub fn unknown_errors_return_unknown_test() { let assert Unknown(inner) = unknown echo inner inner |> should.not_equal("") + inner |> string.uppercase |> should.equal(inner) + // confirm the string has been uppercased } // This is necessary to force unknown error generation uniformly across runtimes From dfb052739af2800a1f8f696d1dbd47acd3de40b4 Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt Date: Fri, 26 Dec 2025 15:16:55 -0600 Subject: [PATCH 08/10] fix my changelog typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c412cdd..fc448e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased ## v2.3.2 - 26 December 2025 -- Fix bug where unknown errors were not properly converted to the Unknown vairant in Erlang ffi. +- Fix bug where unknown errors were not properly converted to the Unknown variant in Erlang ffi. ## v2.3.1 - 6 November 2025 - Fix bug with error string typo for `Enotdir` on JS. From 3d4a61973a183c2f4d92748e33b2ee2420739f07 Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt Date: Fri, 26 Dec 2025 15:18:12 -0600 Subject: [PATCH 09/10] remove lingering echo from test --- test/simplifile_test.gleam | 1 - 1 file changed, 1 deletion(-) diff --git a/test/simplifile_test.gleam b/test/simplifile_test.gleam index 872f5a2..0cbe3c2 100644 --- a/test/simplifile_test.gleam +++ b/test/simplifile_test.gleam @@ -772,7 +772,6 @@ pub fn unknown_errors_return_unknown_test() { let assert Error(unknown) = err let assert Unknown(inner) = unknown - echo inner inner |> should.not_equal("") inner |> string.uppercase |> should.equal(inner) // confirm the string has been uppercased From 8f44c915d6cb2b07d501b51ffbafb3025c561f6f Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt Date: Fri, 26 Dec 2025 15:22:01 -0600 Subject: [PATCH 10/10] update to 1.13 version of gleam --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f417b1c..1870b05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: - uses: erlef/setup-beam@v1 with: otp-version: "27" - gleam-version: "1.9.0" + gleam-version: "1.13.0" rebar3-version: "3" # elixir-version: "1.14.2" - run: gleam test