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

Map Forbidden File IO exceptions to 403 #402

Merged
merged 10 commits into from
Oct 29, 2024

Conversation

andrew4699
Copy link
Contributor

@andrew4699 andrew4699 commented Oct 23, 2024

Description

Makes Forbidden exceptions during File IO return a 403.

1 line change turned into 1000 lines of test changes...

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • Integration test

Checklist:

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings

@andrew4699 andrew4699 force-pushed the aguterman-file-io-error-status-codes branch from c2117b4 to 603c2e8 Compare October 24, 2024 23:19
@andrew4699 andrew4699 requested a review from flyrain October 25, 2024 19:55
Comment on lines +39 to +40
private final Optional<Supplier<RuntimeException>> newInputFileExceptionSupplier;
private final Optional<Supplier<RuntimeException>> newOutputFileExceptionSupplier;
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional is designed for method return value, but not for a class field or method parameter. It adds overhead and confusing without much benefits. This double-wrapping(Optional<Supplier<RuntimeException>>) raises a bit more questions:

  1. Is the Supplier optional, or is the result of the supplier function (the RuntimeException) optional?
  2. Why does the exception need to be wrapped in a supplier rather than passed directly?

Could we use a simpler form like this:

RuntimeException newInputFileException;

Then we can throw it by checking if it's null:

if(newInputFileException != null) throw newInputFileException;

Or if you really want to use optional, we can wrap it within a getter like this, and invoke it from callers:

Optional<RuntimeException> getNewInputFileException(){
   return Optional.ofNullable(newInputFileException);
} 

I think using null is perfectly fine here. This simplifies all callers in the chain.

Copy link
Contributor

Choose a reason for hiding this comment

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

It adds overhead and confusing without much benefits. This double-wrapping(Optional<Supplier>) raises a bit more questions:

What exactly is unclear about Optional<Supplier<...>>? It seems pretty obvious from the name that the caller may optionally provide a supplier.

Is the Supplier optional, or is the result of the supplier function (the RuntimeException) optional?

In fact with Optional<Supplier<..>> the caller has only one choice and it's checked at compile time. The supplier itself is optional.

On the other hand, if we rely on null, they can either pass a null supplier or a supplier that yields null. Which is it?

Why does the exception need to be wrapped in a supplier rather than passed directly?

In this case I think it's fine to remove the supplier, but relying on the exception itself being null isn't that much more clear.

Copy link
Contributor

Choose a reason for hiding this comment

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

Using Optional here adds cognitive burden for the reader, especially since Optional is typically used to facilitate fluent method chaining. This additional complexity can be confusing, particularly for someone quickly scanning the code. Plus, it doesn't add any benefit here.

On the other hand, if we rely on null, they can either pass a null supplier or a supplier that yields null. Which is it?

() -> null is not a null supplier, right?

Copy link
Contributor Author

@andrew4699 andrew4699 Oct 28, 2024

Choose a reason for hiding this comment

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

Since it's a test, the exception technically doesn't have to be wrapped in a Supplier, but more generally exceptions generate their stack trace at construction time so using a Supplier makes the stack trace accurate.

Regarding Optional vs. null vs. () -> null, I don't have a strong preference. They all seem equally (un)confusing and the reader can look at the comments to figure out the meaning.

Copy link
Contributor

Choose a reason for hiding this comment

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

Supplier makes sense if we want accurate stack trace.

I don't think it's necessary to use Optional though. Optional<Supplier<RuntimeException>> can also accept () -> null, as the supplier is not null. BTW, we may check if s.get() is null here, as it may throw a null here.

   newInputFileExceptionSupplier.ifPresent(
        s -> {
          throw s.get();
        });

Copy link
Contributor

@eric-maynard eric-maynard Oct 28, 2024

Choose a reason for hiding this comment

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

Yes, Optional<A<B<..>> can always accept the A being null, or the B being null, or B.c.x.y.z being null. But the point of optional types is to make it obvious to the caller exactly at what level the optionality lies. Without it, they are left to read the code to guess what exact reference needs to be null (if any) for things to work.

Instead of pursuing 110% null-safety at every level of the object hierarchy across all APIs, it's better to be explicit and use Optional to clearly demarcate what objects are "optional". If the caller passes in a null for A.B.c even though A was optional, this is pretty clearly a bug and not an appropriate way to disable behavior. That's less clear if A is not optional and we are using nulls as flow control.

Having said all of this, it's a test class. Because of that I think it's fair to expect folks using this to read the code, and so all of this doesn't matter very much. I am okay merging this either way.

Copy link
Contributor

@flyrain flyrain Oct 28, 2024

Choose a reason for hiding this comment

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

Makes sense to pursue null-safety. The Optional isn't designed for that though. It works in a clumsy way to achieve that, you will have to add Optional to the signatures of all callers, and serialization won't work IIUC. It doesn't quite matter here anyway. I'm fine with either one.

@flyrain flyrain enabled auto-merge (squash) October 28, 2024 23:40
@flyrain flyrain merged commit 9e8a87a into apache:main Oct 29, 2024
5 checks passed
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.

4 participants