dream_test/file

File operations for dream_test.

Dream Test uses this module internally for snapshot testing and Gherkin file parsing. It wraps Erlang file operations with a small, structured error type (FileError) so callers can decide how to handle failures.

Example

Use this in test code (for example inside a snippet tests() function).

let path = tmp_path()

// Setup: create the file (no assertions during setup)
use _ <- result.try(
  write(path, "hello") |> result.map_error(error_to_string),
)

read(path)
|> should
|> be_equal(Ok("hello"))
|> or_fail_with("expected to read back written content")

Types

Errors that can occur during file operations.

Each variant captures the file path and provides specific information about what went wrong. Use error_to_string to format for display.

Variants

VariantCause
NotFoundFile or directory does not exist
PermissionDeniedNo read/write permission for the path
IsDirectoryExpected a file, got a directory
NoSpaceDisk full or quota exceeded
FileSystemErrorOther OS-level error (with reason string)

Example

error_to_string(NotFound("/x"))
|> should
|> be_equal("File not found: /x")
|> or_fail_with("expected NotFound formatting")
pub type FileError {
  NotFound(path: String)
  PermissionDenied(path: String)
  IsDirectory(path: String)
  NoSpace(path: String)
  FileSystemError(path: String, reason: String)
}

Constructors

  • NotFound(path: String)

    File or directory not found.

    The path field contains the missing path. This is the most common error—returned when trying to read a file that doesn’t exist.

  • PermissionDenied(path: String)

    Permission denied for the operation.

    The current user lacks read or write permission for this path. On Unix systems, check the file permissions with ls -la.

  • IsDirectory(path: String)

    Path is a directory, not a file.

    Returned when attempting file operations (read/write) on a directory.

  • NoSpace(path: String)

    No space left on device.

    The disk is full or the user has exceeded their quota. Free up space or write to a different location.

  • FileSystemError(path: String, reason: String)

    Other file system error with the raw reason.

    This captures any error not covered by the specific variants above. The reason field contains the Erlang error atom as a string (e.g., “ebusy”, “emfile”).

Values

pub fn delete(path path: String) -> Result(Nil, FileError)

Delete a file.

This operation is idempotent: deleting a file that doesn’t exist returns Ok(Nil), not an error. This makes cleanup code simpler.

Parameters

  • path - Path to the file to delete

Returns

  • Ok(Nil) - File was deleted (or didn’t exist)
  • Error(PermissionDenied) - Can’t delete the file
  • Error(IsDirectory) - Path is a directory (use rmdir)
  • Error(FileSystemError) - Other OS error

Examples

let path = tmp_path()

// Setup: create the file, then delete it (no assertions during setup)
use _ <- result.try(
  write(path, "hello") |> result.map_error(error_to_string),
)
use _ <- result.try(delete(path) |> result.map_error(error_to_string))

read(path)
|> should
|> be_equal(Error(NotFound(path)))
|> or_fail_with("expected deleted file to be NotFound")
pub fn delete_files_matching(
  directory directory: String,
  extension extension: String,
) -> Result(Int, FileError)

Delete all files in a directory that have a specific extension.

Searches the given directory (non-recursively) for files matching the extension pattern and deletes them. Returns the count of files actually deleted.

Parameters

  • directory - Path to the directory to search
  • extension - File extension including the dot (e.g., “.snap”, “.tmp”)

Returns

  • Ok(Int) - Number of files deleted
  • Error(FileError) - Directory access failed (the specific variant depends on the underlying file system error)

Examples

let directory = "./test/tmp/file_helpers_" <> int.to_string(unique_port())
let a = directory <> "/a.snap"
let b = directory <> "/b.snap"
let keep = directory <> "/keep.txt"

// Setup: create 2 matching files and 1 non-matching file
use _ <- result.try(write(a, "a") |> result.map_error(error_to_string))
use _ <- result.try(write(b, "b") |> result.map_error(error_to_string))
use _ <- result.try(
  write(keep, "keep") |> result.map_error(error_to_string),
)

delete_files_matching(directory, ".snap")
|> should
|> be_equal(Ok(2))
|> or_fail_with("expected two deleted snapshots")

Notes

  • Only searches the immediate directory (not subdirectories)
  • Files that can’t be deleted are silently skipped
  • The count reflects only successfully deleted files
pub fn error_to_string(error error: FileError) -> String

Convert a FileError to a human-readable string.

Formats the error with both the error type and the affected path, suitable for logging or displaying to users.

Parameters

  • error: the FileError value to format

Returns

A human-readable message string. This function is pure (no I/O).

Examples

error_to_string(NotFound("/x"))
|> should
|> be_equal("File not found: /x")
|> or_fail_with("expected NotFound formatting")
pub fn read(path path: String) -> Result(String, FileError)

Read the entire contents of a file as a UTF-8 string.

Returns the file contents on success, or a FileError describing what went wrong.

Parameters

  • path - Path to the file (absolute or relative to cwd)

Returns

  • Ok(String) - The file contents
  • Error(NotFound) - File doesn’t exist
  • Error(PermissionDenied) - Can’t read the file
  • Error(IsDirectory) - Path is a directory
  • Error(FileSystemError) - Other OS error

Examples

let path = tmp_path()

// Setup: create the file (no assertions during setup)
use _ <- result.try(
  write(path, "hello") |> result.map_error(error_to_string),
)

read(path)
|> should
|> be_equal(Ok("hello"))
|> or_fail_with("expected to read back written content")
pub fn write(
  path path: String,
  content content: String,
) -> Result(Nil, FileError)

Write a string to a file, creating parent directories if needed.

If the file exists, it will be completely overwritten. If parent directories don’t exist, they will be created automatically.

Parameters

  • path - Destination path (absolute or relative to cwd)
  • content - String content to write

Returns

  • Ok(Nil) - File written successfully
  • Error(PermissionDenied) - Can’t write to the path
  • Error(NoSpace) - Disk is full
  • Error(IsDirectory) - Path is a directory
  • Error(FileSystemError) - Other OS error

Examples

let path = tmp_path()

// Setup: create the file (no assertions during setup)
use _ <- result.try(
  write(path, "hello") |> result.map_error(error_to_string),
)
Search Document