# Roadmap
`file-ext` (v12.0.0) is a small Rust crate of filesystem utility functions
(file, directory, path, symlink, user helpers). This document captures the
gaps found by reviewing the current source (`src/`), tests, tooling, and docs,
and lays out a prioritized plan to close them.
## Current state (for reference)
- **File**: read (full/partial), create, write (append-only, requires file to
pre-exist), delete, copy (plain + chunked with progress/cancel callbacks),
length, modification timestamp, read-or-create-and-write.
- **Directory**: exists, create (recursive), delete (recursive, shells out to
`rm -rf` / `rd /s /q`).
- **Path**: separator, root, `..`, build path from parts, working directory,
temp folder path.
- **Symlink**: exists, create, is-symlink, points-to, resolve relative target.
- **User**: current user (unix/windows), current user domain (windows only).
- **Input validation**: `FilterString::is_valid_input_string` blocks control
chars and shell metacharacters (whitespace, quotes, `&`, `|`, `;`) — used
inconsistently (see Gaps).
- **Tests**: ~1060 lines of unit tests plus doctests embedded in `lib.rs`.
No CI workflow exists in the repo (no `.github/workflows`), so tests only
run when a human runs `cargo test` locally.
## Gaps
### 1. Missing core API surface
- **No directory listing** (`read_dir` equivalent). There's no way to
enumerate files/subdirectories, which is a basic expectation for a
file-utility crate and is needed before `delete_directory`/`copy` style
recursive operations can be reimplemented in pure Rust (see #2).
- **No move/rename** (`fs::rename` wrapper). Only copy + delete exist, which
is not atomic and is wasteful for same-volume moves.
- **No file/directory metadata beyond size and mtime** — no permissions
(readonly/executable bits), no created-time, no accessed-time, no
`is_file`/`is_dir`/`file_type` convenience beyond the boolean exists checks.
- **No recursive directory copy** — only single-file copy exists; copying a
directory tree is a common companion operation to `delete_directory`.
- **`write_file` cannot create-or-truncate** — it opens with
`.create(false).truncate(false)` and always seeks to `SeekFrom::End(0)`, so
it only *appends* to an existing file. There is no "overwrite/truncate"
primitive and no way to write at an arbitrary offset. This is surprising
given the function is named `write_file` rather than `append_file`.
- **No symlink deletion helper** — symlinks are deleted via the generic
`delete_file`, which works on Unix but should be verified/documented for
Windows directory-symlinks (`rd` vs `remove_file` semantics differ).
- **User module is thin** — no `get_current_user` for anything beyond
shelling out to `whoami`; no group info, no home-directory lookup.
### 2. Robustness / correctness
- **`delete_directory` shells out to `rm -Rf` / `rd /s /q`** instead of using
`std::fs::remove_dir_all`. This adds a process-spawn dependency, is slower,
and — more importantly — means the crate's safety validation
(`FilterString::is_valid_input_string`) is the *only* thing standing between
a caller-controlled path and arbitrary shell argument injection. Worth
migrating to `fs::remove_dir_all` (std, no shell involved, cross-platform)
unless there's a specific reason (e.g. Windows long-path handling) the shell
command was chosen — that reason isn't documented anywhere.
- **Inconsistent input validation** — `FilterString::is_valid_input_string` is
called in `file_ext_impl` and `directory_ext_impl` but not in
`symlink_ext_impl` (`create_symlink`, `symlink_points_to`) or
`path_ext_impl`. Symlink creation ultimately writes to disk via
paths built from user input without going through the same filter.
- **`unwrap()` after `is_err()`/`is_some()` checks** (flagged by clippy) in
`symlink_ext_impl::resolve_symlink_path` and
`file_ext_impl::copy_file_with_callbacks_starting_from_byte_and_ending_at_byte`.
Not bugs today, but fragile — a future refactor that reorders the checks
will panic instead of returning `Result::Err`.
- **No handling for zero-length files** in the copy/chunking math
(`end = file_length - 1` etc.) — worth a targeted test; integer underflow
(`file_length - 1` when `file_length == 0`) would panic in debug builds.
- **Dead code**: `Symbol` struct has ~12 fields clippy reports as never read
(`new_line`, `carriage_return`, `equals`, `comma`, `hyphen`, `colon`, etc.) —
either they're unused and should be removed, or they're part of the public
contract for consumers and should be `#[allow(dead_code)]`'d with a comment
explaining why.
### 3. Testing & CI
- **No CI pipeline at all.** There's no `.github/workflows/`, so `cargo test`,
`cargo clippy`, and cross-platform verification (the README explicitly
claims Linux/macOS/Windows support) only happen when someone remembers to
run them locally. Given the crate has Windows-specific and Unix-specific
`#[cfg(...)]` code paths that can't be compile-checked on a single dev
machine, this is the highest-leverage gap to close.
- **`cargo clippy` currently reports 40 warnings** (mostly doctest `#[test]`
attributes, a few real logic nits — see above). None are gated in CI
because there is no CI.
- **No test coverage for Windows-only code** (`get_current_user_domain`,
Windows symlink creation, Windows temp-folder path) can run in this
environment — needs a Windows CI runner to ever be exercised.
- **No fuzz/property tests** around `FilterString::is_valid_input_string` or
`resolve_symlink_path`, both of which do manual string parsing that's easy
to get subtly wrong on edge cases (empty strings, trailing separators,
repeated `../..`).
- **No benchmarks** for the chunked copy path, despite it being the most
performance-sensitive code in the crate (block size default 100KB is a
hardcoded magic number with no measurement backing it).
### 4. Documentation
- **README feature list is stale** — it lists high-level categories (Read,
Create, Delete, Copy, etc.) but omits several already-shipped functions:
`file_length`, `copy_file_with_callbacks*` (progress/cancel/partial-range
variants), `read_or_create_and_write`, `build_path`/`root`/`folder_up`,
`resolve_symlink_path`, `file_modified_utc`. A new user reading the README
would not discover half the API.
- **No CHANGELOG.md** — 13 tagged releases (1.0.0 → 12.0.0) with no changelog
makes it hard for consumers to know what changed between versions before
upgrading.
- **No CONTRIBUTING.md** — multi-license project (MIT/Apache-2.0/ISC/LGPL-3.0/
CC-BY-4.0) with no guidance on how a contributor should format commits (the
git history is 30+ commits all messaged "wip"), run tests, or pick a
license header for new files.
- **`write_file`'s append-only behavior is not documented** in its doc
comment/README — a new consumer would reasonably expect "write" to mean
"create or overwrite," not "append to an existing file."
### 5. Tooling
- **No `rustfmt.toml` / `clippy.toml`** — no enforced style baseline.
- **No `Cargo.lock` committed** (it's gitignored) — reasonable for a library,
but worth confirming that's intentional rather than incidental.
- **No MSRV (minimum supported Rust version) declared** in `Cargo.toml`,
despite `edition = "2021"` being set.
## Proposed roadmap
### Phase 1 — Safety & CI foundation (do first, low risk)
1. Add `.github/workflows/ci.yml` running `cargo build`, `cargo test`, and
`cargo clippy -- -D warnings` on a matrix of `ubuntu-latest`,
`macos-latest`, `windows-latest` — this is the single highest-value change
since it protects every future change and finally exercises the
Windows-only code paths.
2. Fix the clippy-flagged `unwrap`-after-check patterns (real fragility, not
just style).
3. Route `symlink_ext_impl` path inputs through
`FilterString::is_valid_input_string` for consistency with `file_ext_impl`
/ `directory_ext_impl`.
4. Decide on the `Symbol` dead fields: delete unused ones or annotate with
`#[allow(dead_code)]` + a one-line reason.
5. Add a regression test for zero-length file copy (guards the
`file_length - 1` underflow risk).
### Phase 2 — Close API gaps
6. Add `FileExt::move_file` / `FileExt::rename` (thin wrapper over
`fs::rename`, with a cross-device fallback to copy+delete).
7. Add `FileExt::list_directory` (non-recursive) returning entry names/paths,
as the foundation for:
8. Add `FileExt::copy_directory` (recursive tree copy), built on #7.
9. Consider replacing `delete_directory`'s shell-out with `fs::remove_dir_all`
now that Phase 1 CI can catch any Windows-specific regression; keep the
shell-based version behind a feature flag only if there's a concrete
compatibility reason to keep it.
10. Split `write_file` semantics: keep current append behavior (rename or
document clearly) and add an explicit `overwrite_file` /
`write_file_at_offset` for callers who want create-or-truncate or
positional writes.
11. Add basic metadata accessors: `is_readonly`, `set_readonly`,
`file_created_utc`, `file_accessed_utc`.
### Phase 3 — Documentation & release hygiene
12. Regenerate the README feature list from the actual public API in
`lib.rs` (or add a doc test / script that fails CI if they drift).
13. Add `CHANGELOG.md` starting from 12.0.0 going forward.
14. Add a short `CONTRIBUTING.md` (commit message convention, how to run
`cargo fmt`/`cargo clippy`/`cargo test` before a PR, license header
expectations given the multi-license setup).
15. Document `write_file`'s append semantics explicitly in its rustdoc and
the README.
### Phase 4 — Hardening & performance (lower urgency)
16. Property/fuzz tests for `FilterString::is_valid_input_string` and
`SymlinkExtImpl::resolve_symlink_path`.
17. Benchmarks (`cargo bench` via `criterion`) for the chunked copy path to
validate/tune the 100KB default block size.
18. Declare an MSRV in `Cargo.toml` and check it in CI.
## Suggested sequencing
Phase 1 is small, low-risk, and unlocks safe iteration on everything else —
do it first regardless of what else gets prioritized. Phases 2–4 can be
reordered based on actual consumer demand (e.g., if no one's asked for
`move_file`, it can wait behind documentation fixes).