/* * 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 testing whether a path exists, is a directory, is a regular file, or is a symbolic link, and its functions.///pubmod Fs.FileTest {use Fs.FileStatuse Fs.FileTestuse Fs.FsLayer.{logPre, logPost}use IoError.IoErroruse IoError.ErrorKind////// Effect for testing whether a path exists, is a directory, is a regular file, or is a symbolic link.///pubeffFileTest {////// Returns `true` if the given file `f` exists.///defexists(f: String): Result[IoError, Bool]////// Returns `true` if the given file `f` is a directory.///defisDirectory(f: String): Result[IoError, Bool]////// Returns `true` if the given file `f` is a regular file.///defisRegularFile(f: String): Result[IoError, Bool]////// Returns `true` if the given file `f` is a symbolic link.///defisSymbolicLink(f: String): Result[IoError, Bool] }////// Handles the `FileTest` effect of the given function `f`.////// In other words, re-interprets the `FileTest` effect using the `IO` effect.///pubdefhandle(f: a -> b \ ef): a -> b \ (ef - FileTest) + IO = x ->checked_ecast(Fs.FileSystem.handle(y ->checked_ecast(Fs.FileStat.runWithFileSystem(() -> runWithFileStat(() -> f(y)) ) ) )(x) )////// Runs the `FileTest` effect of the given function `f`.////// In other words, re-interprets the `FileTest` effect using the `IO` effect.///@DefaultHandlerpubdefrunWithIO(f: Unit -> a \ ef): a \ (ef - FileTest) + IO = handle(f)()////// Handles the `FileTest` effect of the given function `f` using the `FileStat` effect.///pubdefrunWithFileStat(f: Unit -> a \ ef): a \ (ef - FileTest) + FileStat =run { f() } withhandlerFileTest {defexists(filename, k) = k(Fs.FileStat.exists(filename))defisDirectory(filename, k) = k(Fs.FileStat.isDirectory(filename))defisRegularFile(filename, k) = k(Fs.FileStat.isRegularFile(filename))defisSymbolicLink(filename, k) = k(Fs.FileStat.isSymbolicLink(filename)) }////// Middleware that intercepts the `FileTest` 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 - FileTest) + { FileTest, Logger } =run { f() } withhandlerFileTest {defexists(filename, k) = {logPre("exists", filename);letresult = FileTest.exists(filename);logPost("exists", filename, b -> "${b}", result);k(result) }defisDirectory(filename, k) = {logPre("isDirectory", filename);letresult = FileTest.isDirectory(filename);logPost("isDirectory", filename, b -> "${b}", result);k(result) }defisRegularFile(filename, k) = {logPre("isRegularFile", filename);letresult = FileTest.isRegularFile(filename);logPost("isRegularFile", filename, b -> "${b}", result);k(result) }defisSymbolicLink(filename, k) = {logPre("isSymbolicLink", filename);letresult = FileTest.isSymbolicLink(filename);logPost("isSymbolicLink", filename, b -> "${b}", result);k(result) } }////// Middleware that intercepts the `FileTest` 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 - FileTest) + FileTest =letr = p -> Fs.FsLayer.resolve(baseDir, p);run { f() } withhandlerFileTest {defexists(filename, k) = k(FileTest.exists(r(filename)))defisDirectory(filename, k) = k(FileTest.isDirectory(r(filename)))defisRegularFile(filename, k) = k(FileTest.isRegularFile(r(filename)))defisSymbolicLink(filename, k) = k(FileTest.isSymbolicLink(r(filename))) }////// Middleware that intercepts the `FileTest` 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 - FileTest) + FileTest =letc = p -> Fs.FsLayer.chroot(chrootDir, p);run { f() } withhandlerFileTest {defexists(filename, k) = k(Result.flatMap(p -> FileTest.exists(p), c(filename)))defisDirectory(filename, k) = k(Result.flatMap(p -> FileTest.isDirectory(p), c(filename)))defisRegularFile(filename, k) = k(Result.flatMap(p -> FileTest.isRegularFile(p), c(filename)))defisSymbolicLink(filename, k) = k(Result.flatMap(p -> FileTest.isSymbolicLink(p), c(filename))) }////// Middleware that intercepts the `FileTest` 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 - FileTest) + FileTest =letc = p -> Fs.FsLayer.allowList(allowedDirs, p);run { f() } withhandlerFileTest {defexists(filename, k) = k(Result.flatMap(p -> FileTest.exists(p), c(filename)))defisDirectory(filename, k) = k(Result.flatMap(p -> FileTest.isDirectory(p), c(filename)))defisRegularFile(filename, k) = k(Result.flatMap(p -> FileTest.isRegularFile(p), c(filename)))defisSymbolicLink(filename, k) = k(Result.flatMap(p -> FileTest.isSymbolicLink(p), c(filename))) }////// Middleware that intercepts the `FileTest` 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 - FileTest) + FileTest =letc = p -> Fs.FsLayer.denyList(deniedDirs, p);run { f() } withhandlerFileTest {defexists(filename, k) = k(Result.flatMap(p -> FileTest.exists(p), c(filename)))defisDirectory(filename, k) = k(Result.flatMap(p -> FileTest.isDirectory(p), c(filename)))defisRegularFile(filename, k) = k(Result.flatMap(p -> FileTest.isRegularFile(p), c(filename)))defisSymbolicLink(filename, k) = k(Result.flatMap(p -> FileTest.isSymbolicLink(p), c(filename))) }////// Middleware that intercepts the `FileTest` 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 - FileTest) + FileTest =letc = p -> Fs.FsLayer.allowGlob(patterns, p);run { f() } withhandlerFileTest {defexists(filename, k) = k(Result.flatMap(p -> FileTest.exists(p), c(filename)))defisDirectory(filename, k) = k(Result.flatMap(p -> FileTest.isDirectory(p), c(filename)))defisRegularFile(filename, k) = k(Result.flatMap(p -> FileTest.isRegularFile(p), c(filename)))defisSymbolicLink(filename, k) = k(Result.flatMap(p -> FileTest.isSymbolicLink(p), c(filename))) }////// Middleware that intercepts the `FileTest` 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 - FileTest) + FileTest =letc = p -> Fs.FsLayer.denyGlob(patterns, p);run { f() } withhandlerFileTest {defexists(filename, k) = k(Result.flatMap(p -> FileTest.exists(p), c(filename)))defisDirectory(filename, k) = k(Result.flatMap(p -> FileTest.isDirectory(p), c(filename)))defisRegularFile(filename, k) = k(Result.flatMap(p -> FileTest.isRegularFile(p), c(filename)))defisSymbolicLink(filename, k) = k(Result.flatMap(p -> FileTest.isSymbolicLink(p), c(filename))) }////// Middleware that intercepts the `FileTest` effect, resolving all file paths/// to their real filesystem targets by following symlinks.////// - Bool ops (`exists`, `isDirectory`, `isRegularFile`): resolve path; return `Ok(false)` for dangling symlinks./// - `isSymbolicLink`: passed through unchanged (always checks the link itself).///pubdefwithFollowLinks(f: Unit -> a \ ef): a \ (ef - FileTest) + FileTest =letresolveBool = (path, op) -> matchFs.FsLayer.resolveRealPath(path) {case Ok(p) => op(p)case Err(IoError(ErrorKind.NotFound, _)) => Ok(false)case Err(e) => Err(e) };run { f() } withhandlerFileTest {defexists(filename, k) = k(resolveBool(filename, p -> FileTest.exists(p)))defisDirectory(filename, k) = k(resolveBool(filename, p -> FileTest.isDirectory(p)))defisRegularFile(filename, k) = k(resolveBool(filename, p -> FileTest.isRegularFile(p)))defisSymbolicLink(filename, k) = k(FileTest.isSymbolicLink(filename)) }}