dream_test/file

File operations for dream_test.

This module provides file I/O operations for internal use by dream_test, particularly for snapshot testing and Gherkin file parsing. It wraps Erlang’s file operations with proper error handling.

Error Handling

Unlike many file libraries that return opaque errors, this module provides structured FileError types that tell you exactly what went wrong:

case file.read("config.json") {
  Ok(content) -> parse(content)
  Error(NotFound(_)) -> use_defaults()
  Error(PermissionDenied(path)) -> panic as "Cannot read " <> path
  Error(error) -> panic as file.error_to_string(error)
}

Usage Examples

Reading Files

import dream_test/file

case file.read("./test/fixtures/expected.json") {
  Ok(content) -> content
  Error(error) -> {
    io.println("Error: " <> file.error_to_string(error))
    ""
  }
}

Writing Files

// Creates parent directories automatically
case file.write("./test/snapshots/output.snap", result) {
  Ok(Nil) -> io.println("Saved!")
  Error(NoSpace(_)) -> io.println("Disk full!")
  Error(error) -> io.println(file.error_to_string(error))
}

Deleting Files

// Safe to call even if file doesn't exist
let _ = file.delete("./test/snapshots/old.snap")

// Delete all snapshots
case file.delete_files_matching("./test/snapshots", ".snap") {
  Ok(count) -> io.println("Deleted " <> int.to_string(count) <> " files")
  Error(error) -> io.println(file.error_to_string(error))
}

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

case file.read("secret.txt") {
  Ok(content) -> use(content)
  Error(PermissionDenied(path)) -> {
    io.println("Access denied: " <> path)
    io.println("Try: chmod +r " <> path)
  }
  Error(NotFound(path)) -> {
    io.println("File not found: " <> path)
  }
  Error(error) -> {
    io.println(file.error_to_string(error))
  }
}
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: 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

// Safe cleanup - doesn't fail if already deleted
let _ = file.delete("./test/temp/output.txt")
// Delete with error handling
case file.delete(snapshot_path) {
  Ok(Nil) -> io.println("Snapshot cleared")
  Error(PermissionDenied(_)) -> io.println("Cannot delete (permission denied)")
  Error(error) -> io.println(file.error_to_string(error))
}
pub fn delete_files_matching(
  directory: String,
  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(FileSystemError) - Directory access failed

Examples

// Clear all snapshot files
case file.delete_files_matching("./test/snapshots", ".snap") {
  Ok(0) -> io.println("No snapshots to delete")
  Ok(n) -> io.println("Deleted " <> int.to_string(n) <> " snapshots")
  Error(error) -> io.println(file.error_to_string(error))
}
// Clean up temporary files before test run
let _ = file.delete_files_matching("./test/temp", ".tmp")

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: 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.

Examples

error_to_string(NotFound("/app/config.json"))
// -> "File not found: /app/config.json"

error_to_string(PermissionDenied("/etc/shadow"))
// -> "Permission denied: /etc/shadow"

error_to_string(FileSystemError("/dev/null", "ebusy"))
// -> "File error (ebusy): /dev/null"
pub fn read(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

// Read a configuration file
case file.read("gleam.toml") {
  Ok(toml) -> parse_config(toml)
  Error(NotFound(_)) -> default_config()
  Error(error) -> panic as file.error_to_string(error)
}
// Read with full error handling
case file.read(path) {
  Ok(content) -> Ok(content)
  Error(NotFound(_)) -> Error("Config file missing")
  Error(PermissionDenied(_)) -> Error("Cannot read config (permission denied)")
  Error(error) -> Error(file.error_to_string(error))
}
pub fn write(
  path: String,
  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

// Write a snapshot file
case file.write("./test/snapshots/user.snap", json_output) {
  Ok(Nil) -> io.println("Snapshot saved")
  Error(error) -> io.println("Failed: " <> file.error_to_string(error))
}
// Creates nested directories automatically
file.write("./deep/nested/path/file.txt", "content")
// Creates ./deep/nested/path/ if it doesn't exist
Search Document