Last week I signed up for an issue in Hurl, an HTTP request client and testing tool written in Rust.
--cookie-jar FILE doesn't work if intermediate directories don't exist
#3637
When exporting cookies from a run to SOME_FILE
export is failing if SOME_FILE
has non-existing directory:
$ echo 'HEAD https://hurl.dev' | hurl --cookie-jar /tmp/a/b/c/out.txt
error: Issue writing to /tmp/a/b/c/out.txt: Os { code: 2, kind: NotFound, message: "No such file or directory" }
--cookie-jar
should works like --curll
which create intermediate directories:
$ echo 'HEAD https://hurl.dev' | hurl --curl /tmp/a/b/c/out.txt
Note: we could try while fixing this bug to mutualize the code that create a file and it's indermediate directories:
// We ensure that parent folder is created.
if let Some(parent) = filename.parent() {
match std::fs::create_dir_all(parent) {
Ok(_) => {}
Err(err) => {
return Err(ReportError::from_error(
err,
filename,
"Issue writing TAP report",
))
}
}
}
let mut file = match File::create(filename) {
Ok(f) => f,
Err(e) => {
return Err(ReportError::from_error(
e,
filename,
"Issue writing TAP report",
))
}
};
I needed to fix a command that accepted a file path so that it would create the parent directories in the path if they didn't exist.
My knowledge of Rust is rudimentary at best - if you asked me a week ago I would've told you it was nonexistent. When I started working on this my immediate thought process was to go and "learn Rust" - as in go read the Rust book. I was in the middle of reading what an integer was when I realized it probably wasn't the best use of my time.
Thinking back to my previous experience working in unfamiliar languages and with unknown technologies, I figured I probably don't necessarily need to "know Rust" to be able to contribute. I worked with Go a little bit last year without needing to go read the Go documentation, so I figured I might as well do what I did last time and just jump in.
Once I started actually reading the code, I was able to leverage fundamental programming concepts - I could look at the code and understand I was looking at a function, with type annotations for return types. I could understand I was looking at error handling logic, even though the syntax was quite different from what I'm used to in JavaScript-land, where I usually live.
Even though my first reaction was "Why did I sign up for this?", with that approach, the linter, the unit tests, and some pointers from one of the maintainers, I was able to get the job done.
According to the issue, the logic for this already existed elsewhere in the source code, so I just had to find it and add it to the function that handled the command. Here's the snippet I used:
// We ensure that parent folder is created.
if let Some(parent) = filename.parent() {
match std::fs::create_dir_all(parent) {
Ok(_) => {}
Err(err) => {
return Err(CliError::IO(format!(
"Issue writing to {}: {err:?}",
filename.display()
)));
}
}
}
Create parent folders if missing when using --cookie-jar FILE
#3688
Fixes #3637
Please let me know if there's anything I could improve. Thanks.
In addition to the fix, the maintainer also asked me to do some refactoring, by extracting the parent directory creation logic into a util function and replacing the existing logic with that function throughout the source code. They helped me out by providing some snippets of how they'd prefer it to be done, and by this point I was a little bit more comfortable reading and writing Rust, so it wasn't too bad.
Here's the same logic but as a function:
// Create parent directories, if missing, given a filepath ending with a file name
pub fn create_dir_all(filename: &Path) -> Result<(), std::io::Error> {
if let Some(parent) = filename.parent() {
return std::fs::create_dir_all(parent);
}
Ok(())
}
You might notice this function is calling a standard library function with the same name. That threw me off at first - why was I writing a function when there already is one that does the same thing? The difference is that the standard library function requires a directory path as the argument and doesn't work when it ends in a file name, while this function wraps around it and strips the file name before calling it.
With that in place, my changes were accepted. Would I feel confident putting Rust on my resume? Probably not (yet). But it was fun to explore nevertheless.
Top comments (0)