/* * Copyright 2026 Flix Authors * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. */////// Effect for reading the contents of a file as a string, as lines, or as bytes, and its functions.///pubmod Fs.FileRead {use Fs.FileReaduse Fs.FileStatuse Fs.FileSystemuse Fs.FsLayer.{logPre, logPost}use Fs.Size////// Effect for reading the contents of a file as a string, as lines, or as bytes.///pubeffFileRead {////// Returns a string of all the contents of the given file `f`.///defread(f: String): Result[IoError, String]////// Returns a list of all lines in the given file `f`.///defreadLines(f: String): Result[IoError, List[String]]////// Returns a vector of all the bytes in the given file `f`.///defreadBytes(f: String): Result[IoError, Vector[Int8]] }////// Handles the `FileRead` effect of the given function `f`.////// In other words, re-interprets the `FileRead` effect using the `IO` effect.///pubdefhandle(f: a -> b \ ef): a -> b \ (ef - FileRead) + IO = x ->checked_ecast(Fs.FileSystem.handle(y -> runWithFileSystem(() -> f(y)) )(x) )////// Runs the `FileRead` effect of the given function `f`.////// In other words, re-interprets the `FileRead` effect using the `IO` effect.///@DefaultHandlerpubdefrunWithIO(f: Unit -> a \ ef): a \ (ef - FileRead) + IO = handle(f)()////// Handles the `FileRead` effect of the given function `f` using the `FileSystem` effect.///pubdefrunWithFileSystem(f: Unit -> a \ ef): a \ (ef - FileRead) + FileSystem =run { f() } withhandlerFileRead {defread(filename, k) = k(Fs.FileSystem.read(filename))defreadLines(filename, k) = k(Fs.FileSystem.readLines(filename))defreadBytes(filename, k) = k(Fs.FileSystem.readBytes(filename)) }////// Middleware that intercepts the `FileRead` effect, logging each operation/// and its result via the `Logger` effect.////// Successful operations are logged at `Debug` level; errors at `Warn`.///pubdefwithLogging(f: Unit -> a \ ef): a \ (ef - FileRead) + { FileRead, Logger } =run { f() } withhandlerFileRead {defread(filename, k) = {logPre("read", filename);letresult = FileRead.read(filename);logPost("read", filename, s -> "${String.length(s)} chars", result);k(result) }defreadLines(filename, k) = {logPre("readLines", filename);letresult = FileRead.readLines(filename);logPost("readLines", filename, ls -> "${List.length(ls)} lines", result);k(result) }defreadBytes(filename, k) = {logPre("readBytes", filename);letresult = FileRead.readBytes(filename);logPost("readBytes", filename, bs -> "${Vector.length(bs)} bytes", result);k(result) } }////// Middleware that intercepts the `FileRead` effect, resolving all file paths/// relative to the given `baseDir` using Java's `Path.resolve` semantics.////// Absolute paths are passed through unchanged.///pubdefwithBaseDir(baseDir: String, f: Unit -> a \ ef): a \ (ef - FileRead) + FileRead =letr = p -> Fs.FsLayer.resolve(baseDir, p);run { f() } withhandlerFileRead {defread(filename, k) = k(FileRead.read(r(filename)))defreadLines(filename, k) = k(FileRead.readLines(r(filename)))defreadBytes(filename, k) = k(FileRead.readBytes(r(filename))) }////// Middleware that intercepts the `FileRead` effect, validating that all file paths/// resolve within the given `chrootDir`. Rejects paths that escape the chroot with/// a `PermissionDenied` error.///pubdefwithChroot(chrootDir: String, f: Unit -> a \ ef): a \ (ef - FileRead) + FileRead =letc = p -> Fs.FsLayer.chroot(chrootDir, p);run { f() } withhandlerFileRead {defread(filename, k) = k(Result.flatMap(p -> FileRead.read(p), c(filename)))defreadLines(filename, k) = k(Result.flatMap(p -> FileRead.readLines(p), c(filename)))defreadBytes(filename, k) = k(Result.flatMap(p -> FileRead.readBytes(p), c(filename))) }////// Middleware that intercepts the `FileRead` effect, validating that all file paths/// resolve within one of the given `allowedDirs`. Rejects paths outside the/// allow list with a `PermissionDenied` error.///pubdefwithAllowList(allowedDirs: Nel[String], f: Unit -> a \ ef): a \ (ef - FileRead) + FileRead =letc = p -> Fs.FsLayer.allowList(allowedDirs, p);run { f() } withhandlerFileRead {defread(filename, k) = k(Result.flatMap(p -> FileRead.read(p), c(filename)))defreadLines(filename, k) = k(Result.flatMap(p -> FileRead.readLines(p), c(filename)))defreadBytes(filename, k) = k(Result.flatMap(p -> FileRead.readBytes(p), c(filename))) }////// Middleware that intercepts the `FileRead` effect, validating that all file paths/// do not resolve within any of the given `deniedDirs`. Rejects paths inside a/// denied directory with a `PermissionDenied` error.///pubdefwithDenyList(deniedDirs: Nel[String], f: Unit -> a \ ef): a \ (ef - FileRead) + FileRead =letc = p -> Fs.FsLayer.denyList(deniedDirs, p);run { f() } withhandlerFileRead {defread(filename, k) = k(Result.flatMap(p -> FileRead.read(p), c(filename)))defreadLines(filename, k) = k(Result.flatMap(p -> FileRead.readLines(p), c(filename)))defreadBytes(filename, k) = k(Result.flatMap(p -> FileRead.readBytes(p), c(filename))) }////// Middleware that intercepts the `FileRead` effect, validating that all file paths/// match at least one of the given glob `patterns`. Rejects paths that do not match/// with a `PermissionDenied` error.///pubdefwithAllowGlob(patterns: Nel[String], f: Unit -> a \ ef): a \ (ef - FileRead) + FileRead =letc = p -> Fs.FsLayer.allowGlob(patterns, p);run { f() } withhandlerFileRead {defread(filename, k) = k(Result.flatMap(p -> FileRead.read(p), c(filename)))defreadLines(filename, k) = k(Result.flatMap(p -> FileRead.readLines(p), c(filename)))defreadBytes(filename, k) = k(Result.flatMap(p -> FileRead.readBytes(p), c(filename))) }////// Middleware that intercepts the `FileRead` effect, validating that all file paths/// do not match any of the given glob `patterns`. Rejects paths that match a denied/// pattern with a `PermissionDenied` error.///pubdefwithDenyGlob(patterns: Nel[String], f: Unit -> a \ ef): a \ (ef - FileRead) + FileRead =letc = p -> Fs.FsLayer.denyGlob(patterns, p);run { f() } withhandlerFileRead {defread(filename, k) = k(Result.flatMap(p -> FileRead.read(p), c(filename)))defreadLines(filename, k) = k(Result.flatMap(p -> FileRead.readLines(p), c(filename)))defreadBytes(filename, k) = k(Result.flatMap(p -> FileRead.readBytes(p), c(filename))) }////// Middleware that intercepts the `FileRead` effect, resolving all file paths/// to their real filesystem targets by following symlinks.////// Action ops (`read`, `readLines`, `readBytes`): resolve path with fallback to original.///pubdefwithFollowLinks(f: Unit -> a \ ef): a \ (ef - FileRead) + FileRead =letresolve = p -> Fs.FsLayer.resolveRealPathOrSelf(p);run { f() } withhandlerFileRead {defread(filename, k) = k(FileRead.read(resolve(filename)))defreadLines(filename, k) = k(FileRead.readLines(resolve(filename)))defreadBytes(filename, k) = k(FileRead.readBytes(resolve(filename))) }////// Middleware that intercepts the `FileRead` effect, rejecting reads of files/// larger than `maxBytes`. The file size is checked via `FileStat.size` before/// reading. For string-based reads, the limit applies to the on-disk file size/// (not the in-memory string length).///pubdefwithTransferLimit(maxSize: Size, f: Unit -> a \ ef): a \ (ef - FileRead) + { FileRead, FileStat } =letcheck = sz -> Fs.FsLayer.checkTransferLimit(maxSize, sz);run { f() } withhandlerFileRead {defread(filename, k) = k(Result.flatMap(_ -> FileRead.read(filename), Result.flatMap(check, FileStat.size(filename))))defreadLines(filename, k) = k(Result.flatMap(_ -> FileRead.readLines(filename), Result.flatMap(check, FileStat.size(filename))))defreadBytes(filename, k) = k(Result.flatMap(_ -> FileRead.readBytes(filename), Result.flatMap(check, FileStat.size(filename)))) }////// Middleware that intercepts the `FileRead` effect, verifying file checksums/// on read. If a sidecar checksum file exists (e.g., `"data.txt.sha256"` when/// `sidecar` is `".sha256"`), the file's contents are hashed and compared/// against the stored checksum.////// `hash` is a pure function from file bytes to digest bytes./// `sidecar` is the file suffix (e.g., `".sha256"`).////// If no sidecar file exists, the read proceeds normally./// If the checksum does not match, returns `Err(IoError(ChecksumMismatch, ...))`.///pubdefwithChecksum(hash: Vector[Int8] -> Vector[Int8],sidecar: String,f: Unit -> a \ ef ): a \ (ef - FileRead) + FileRead =letcomputeHex = bytes -> Util.Codec.encodeHex(hash(bytes));letdoReadBytes = file -> FileRead.readBytes(file);letdoReadSidecar = file -> FileRead.read(file);run { f() } withhandlerFileRead {defread(filename, k) = k(Fs.FsLayer.checksumOnRead(filename, sidecar, computeHex, FileRead.read(filename), doReadBytes, doReadSidecar))defreadLines(filename, k) = k(Fs.FsLayer.checksumOnRead(filename, sidecar, computeHex, FileRead.readLines(filename), doReadBytes, doReadSidecar))defreadBytes(filename, k) = k(Fs.FsLayer.checksumOnReadBytes(filename, sidecar, computeHex, FileRead.readBytes(filename), doReadSidecar)) }}