Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle ErrorKind NotFound instead of file.exists() #834

Merged
merged 5 commits into from
Sep 26, 2020

Conversation

BProg
Copy link
Contributor

@BProg BProg commented Sep 22, 2020

@charlespierce I am ok with any kind of comments. I would like to know if this is the correct direction you want to take in regards to issue 823.

I have changed only the places that do an open operation on FS, and left alone other operations on FS like rename or delete
There may be some missing places that I could not find

Extend the Fallible struct with 2 new functions to help with converting
the NotFound Err into Ok

Add new functions to the VoltaError struct to be able to determine
if the inner source is of type io::ErrorKind::NotFound
Copy link
Contributor

@charlespierce charlespierce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this @BProg! This looks like it cleans up all of the spots where we were doing the exists() + open() anti-pattern. My comments are mainly around reducing boilerplate and making things more generic.

In general, I think we should try to have the checks for NotFound as close as possible to the IO operation that actually caused them. That will help reduce the amount of times we need to check in separate locations for the same condition, and make it easier to maintain going forward.

crates/volta-migrate/src/v2.rs Outdated Show resolved Hide resolved
crates/volta-core/src/error/mod.rs Outdated Show resolved Hide resolved
Comment on lines 84 to 91
pub trait AcceptableErrorToValue<T> {
fn accept_error_as_value_if<F1, F2>(self, accept_if: F1, as_value: F2) -> Fallible<T>
where
F1: FnOnce(&VoltaError) -> bool,
F2: FnOnce() -> T;
}

pub trait AcceptableErrorToDefault<T>: AcceptableErrorToValue<T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't worked all the way through it, but I think a lot of this machinery could be handled with a helper function and the built-in Result::or_else method, which I think would simplify it a fair amount.

crates/volta-core/src/project/mod.rs Outdated Show resolved Hide resolved
crates/volta-core/src/run/binary.rs Outdated Show resolved Hide resolved
crates/volta-core/src/tool/package/mod.rs Outdated Show resolved Hide resolved
crates/volta-core/src/tool/package_global/uninstall.rs Outdated Show resolved Hide resolved
crates/volta-migrate/src/v1.rs Outdated Show resolved Hide resolved
Comment on lines 97 to 109
let platform_json =
match read_to_string(platform_file).with_context(|| ErrorKind::ReadPlatformError {
file: platform_file.to_owned(),
}) {
Err(error) => {
if error.is_not_found_error_kind() {
return Ok(());
} else {
return Err(error);
}
}
Ok(json) => json,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be simplified a little by removing the call to with_context and instead making it part of the error branch:

let platform_json = match read_to_string(platform_file) {
    Err(error) => {
        if error.kind() == io::ErrorKind::NotFound {
            return Ok(());
        } else {
            return Err(VoltaError::from_source(error, ErrorKind::ReadPlatformError { file: platform_file.to_owned() }));
        }
    }
    Ok(json) => json,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified by using .or_else(ok_if_not_found)

crates/volta-migrate/src/v1.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@charlespierce charlespierce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks so much simpler than the path.exists() checks, thanks @BProg! The remaining comments are primarily around style and cleaning up some clippy warnings, nothing major.

}
Ok(config_src) => RawBinConfig::from_json(config_src)?
.try_into()
.map(|config| Some(config)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clippy raises a warning here: You actually don't need the closure |config| Some(config), you can instead do this with:

Suggested change
.map(|config| Some(config)),
.map(Some),

Comment on lines 13 to 16
use crate::{
error::{Context, ErrorKind, Fallible},
fs::ok_if_not_found,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep the diffs simpler, we've generally tried to keep our use statements to single modules at a time. So instead of grouping the submodules together like this, we would have 2 separate lines:

use crate::error::{Context, ErrorKind, Fallible};
use crate::fs::ok_if_not_found;

(With the appropriate ordering provided by cargo fmt.

true
} else {
// there is no package config - check for orphaned binaries
let remove_orphan_binaries_if_any = || -> Fallible<bool> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we aren't re-using this function anywhere else, I think it would be slightly clearer if this block was included in the match arm below, instead of creating a closure and then calling it.

}
dir_entry_match(&bin_config_dir, |entry| {
let path = entry.path();
if let Ok(Some(config)) = BinConfig::from_file_if_exists(path) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor, but since we're only matching on Ok(Some(_)) here, I think we can leave this line as it was (since it amounts to the same thing):

Suggested change
if let Ok(Some(config)) = BinConfig::from_file_if_exists(path) {
if let Ok(config) = BinConfig::from_file(path) {

};
None
})
.or_else(ok_if_not_found)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so much cleaner than the if exists() { } else { Ok(vec![]) } blocks! 🎉

Comment on lines 14 to 17
use volta_core::{
error::{Context, ErrorKind, Fallible, VoltaError},
fs::ok_if_not_found,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here about keeping the imports separated. It also looks like we actually have a line with use volta_core::fs::{...}, so the additional import can be added to that line instead of a separate one.

Comment on lines 91 to 92
remove_file(&old_volta_bin)
.or_else(ok_if_not_found)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of directly doing remove_file().or_else(), there actually is a helper in volta_core::fs named remove_file_if_exists, so we should re-use that (it internally does the same thing since it's a common pattern).

Comment on lines 99 to 100
remove_file(&old_shim_bin)
.or_else(ok_if_not_found)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here about using remove_file_if_exists instead of remove_file().or_else()

Comment on lines 85 to 86
remove_file(old_layout_file)
.or_else(ok_if_not_found)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well with remove_file_if_exists

Comment on lines 99 to 103
let platform_json = read_to_string(platform_file)
.or_else(ok_if_not_found)
.with_context(|| ErrorKind::ReadPlatformError {
file: platform_file.to_owned(),
})?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens to work because the following line Platform::from_json handles an empty string, but I'm not sure if we should be relying on that behavior. Instead, I think this can be a little more explicit about handling the NotFound error (similar to the implementation of from_file_if_exists above):

let platform_json = match read_to_string(platform_file) {
    Ok(json) => json,
    Err(error) => if error.kind() == io::ErrorKind::NotFound {
        return Ok(());
    } else {
        return Err(VoltaError::from_source(error, ErrorKind::ReadPlatformError { ... }));
    }
};

Copy link
Contributor

@charlespierce charlespierce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, thanks for the help @BProg! ⚡ 🎉

@charlespierce charlespierce merged commit 9d55f4c into volta-cli:main Sep 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants