| Crates.io | path_facts |
| lib.rs | path_facts |
| version | 0.2.1 |
| created_at | 2025-11-02 01:00:07.33273+00 |
| updated_at | 2025-11-10 21:54:23.627054+00 |
| description | You've subscribed to PATH FACTS: Tidy filesystem information to help debug unexpected errors. |
| homepage | |
| repository | https://github.com/schneems/path_facts |
| max_upload_size | |
| id | 1912548 |
| size | 94,537 |
Thanks for subscribing to PATH FACTS. You'll get a fun fact every <15 minutes> reply "TWO ROADS DIVERGED" to unsubscribe!
Unlike the world-famous cat FACTS, path facts only appear when you ask them and are best right
after a std::io::Error from a filesystem operation.
The purpose of path facts is to deliver maximum information about your system on disk in a tidy,
easy-to-understand package. The idea is that it gives you enough information to debug an unexpected
error without requiring you to run around calling ls and cat until you find the problem.
Tired of seeing this:
No such file or directory
When you could be seeing this?
cannot access `/path/to/directory/a/b/c/does_not_exist.txt`
- Prior path is not a directory
- Prior path exists `/path/to/directory/a`
- `/path/to/directory`
└── `a` file [✅ read, ✅ write, ❌ execute]
Then start using path facts today!
I designed this library to be used by other libraries that extend or mimic std::fs so they can
create beautiful errors. Of course, you don't have to be a library author to get your PATH FACTS
fix.
The philosophy behind PATH FACTS is that our systems shouldn't just tell us when something goes wrong, but they should be able to describe WHY it went wrong so that you (the developer) can do something about it. That's the same philosophy I used to introduce a parsing-agnostic syntax suggesting algorithm into Ruby core.
The API is small. Construct a PathFact with a path and Display it as you like:
use path_facts::PathFacts;
let path = std::path::Path::new("doesnotexist.txt");
std::fs::read_to_string(&path)
.map_err(|error| format!("{error}. Path {}", PathFacts::new(&path)))
.unwrap();
For an operation with multiple paths you can use multiple PATH FACTS structs. For example:
use path_facts::PathFacts;
use indoc::formatdoc;
let from = std::path::Path::new("doesnotexist.txt");
let to = std::path::Path::new("also_does_not_exist.txt");
std::fs::rename(&from, to).map_err(|error| formatdoc! {"
cannot rename from `{}` to `{}` due to: {error}.
From path {from_facts}
To path {to_facts}
",
from.display(),
to.display(),
from_facts = PathFacts::new(&from),
to_facts = PathFacts::new(&to)
});
This library uses syncronous file system calls. If you're using this library in an async context,
you'll want to use an async wrapper like tokio::task::block_in_place.
Here are a few facts about paths that some people might find interesting. If you're staring at path facts and an error message, maybe one of these tidbits could help you connect the dots:
execute permissions).execute permission, you're not allowed to view the metadata (such as permission info).write
permission or not!faccess crate for this)If you're considering adopting this library, here are some things you should consider:
path_facts.PathFacts::new on a hot code path. Instead, you could store the path and lazily call
PathFacts only when the error is rendered. We assume that computers do stuff fast and developers
do stuff slowly. You'll be trading off some compute time to reduce end developer debugging time.read permission without read access.
Consider the information provided by path facts as a good starting point on where to focus your
investigation rather than as immutable and indisputable truth.Initial efforts on this library were geared towards giving concrete recommendations such as telling
people to mkdir -p <directory> or running a specific chmod command. That could still be a
worthwhile effort, but that requires that we know both the facts on disk as well as the intent of
the programmer.
To explain: Suggestions and recommendations are better the more true they are. If an error message
says "please try again" and trying again doesn't fix it, but the message continues to assert "please
try again," it's not so much a valid suggestion but more wishful thinking on the error message
author. If a path doesn't exist, that's usually bad, so we suggest you create it via the touch
command. Except if you're trying to create a file, then that path not existing is good, and
suggesting that you create it would introduce an error where none existed before. So, any suggestions
must be context-aware and task-aware.
Complicating things further: implementation details matter when determining the disk's state should be. For example, Rust's std::fs::rename function will error if the "to" path exists unless it's on Unix and it's a directory that is empty and the "from" path is also a directory. But on Windows, the "to" path cannot be a directory. That's a lot of caveats to consider!
Effectively, the only way to deliver truly accurate recommendations for some operations would be to reverse engineer them. That's fine in moderation. We do that a little here, traversing directories in a parent chain to see which one doesn't exist. However, if your implementation is overly coupled to internally described logic, it can be difficult to maintain it if the reference implementation changes without warning. Then suddenly, previously valid suggestions are no longer true!
There are some ways around this inside-out implementation-coupling problem. For example, synax_suggest tries to know as little as possible about Ruby grammar and parsing. It tells the user things that are true, such as "if you take these lines of code together, they're invalid Ruby." Then it presents that truthy information in a way the user can consume and actualize. It's not always a perfect result, but it's correct enough to be helpful more often than it's harmful. A more scholarly way to frame this would be looking at it through the lens of soundeness versus completeness and precision. We aim for "more precise" i.e., "it reports fewer non-errors." It also means we could possibly stop at some point earlier than "completely reverse-engineer rust stdlib behavior" and somewhere further than "simply state the facts". However, we're here. We have the facts, we might as well show those.
Output style and contents are tested via snapshot testing using insta. If the output changes and you want to update the snapshots, you can generate new pending output by running tests cargo test this will produce *.pending-snap files. These can be reviewed and accepted by using cargo insta:
$ cargo insta review
Or via using env vars:
$ INSTA_UPDATE=always cargo test
To run on linux:
$ docker build -f Dockerfile.test -t path_facts_test .
$ docker run --rm path_facts_test