When exceptions are not so exceptional

I’ve been trying to figure out how to handle the authorization layer of a Phoenix app I’m working on which has brought me full circle to something I’ve noticed about the Phoenix defaults since I first started tinkering with it:

The default get! functions in contexts raise an Ecto.NoResultsError which is caught and generally displayed as a 404.

In consideration for authorization, does it make sense to just raise some other sort of exception that’s caught somewhere higher up?

With that said, in reading around, I’m having a hard time understanding how the get! convention meshes with advice about exceptions being “exceptional.” Is a lack of a resource in a repo really exceptional? (And I am not being glib either, this is a serious question). This seems like a case of exception as flow control. Is this observation correct? And if it is, is this an acceptable convention in Elixir?

I found this question on StackOverflow and the accepted answer quotes José but doesn’t seem to provide the source. For reference:

In Elixir, this distinction is rather theoretical, but they matter in some languages like Ruby, where using errors/exceptions for control-flow is expensive because creating the exception object and backtrace is expensive.

Are exceptions as flow-control in Elixir acceptable and, more specifically, do they not incur the same sort of performance hit as Ruby?

Asking specifically since I used throw/catch quite a lot in Ruby. In this situation, I’m debating between some sort of :error/:ok pattern or just raising an exception.

FWIW, performance doesn’t really figure into my reasoning for whether to use throw or raise (or any long jumping) for flow control (which I almost never do). The main reason is that it makes it harder to follow the flow of your code. The more of them you use, the more complex that chain is to unravel.

The pattern I’ve gotten into for authorization is essentially the same as Bodyguard uses. A with block in your controllers that starts with an authorization check, and the block contains the happy path, and the auth check returns an {:error, tuple} on error which becomes the return value of your controller action, and therefore falls through to your FallbackController.

It’s a very simple flow to understand, follow, customize, test, etc.

with is actually my go to right now. I absolutely love it. I caught another topic here where someone mentioned adding a fetch_* to his contexts.

I’m just trying to understand the design choices in Phoenix. One of the hardest things about adopting new languages and frameworks is the whole, “Am I doing this the ‘right way’?” syndrome.

On top of that, with frameworks it’s generally better to do with their conventions rather than against.

It’s just, if I adopt that pattern for authorization, it seems like having it for fetching would be ideal.

When you’re talking about authorization within the context of Phoenix/Plug pipelines, it’s quite straightforward to implement a plug that simply halts/redirects the processing of the current pipeline.

Here’s a super simple pattern I’ve been using for resource authorization lately:

Ok - so again - this is just my take (from over 25 years of working with a wide variety of frameworks, although my exposure to Elixir/Phoenix is fairly recent).

My take has always been that the whole get! missing ID being translated to a 404 convention was because in RESTful routes, the ID is part of the URL, and so a 404 makes sense. That URL/resource literally cannot be found on the server.

My take on why framework authors use exceptions (which a lot do) for this is because it really is exceptional. In normal usage, you really can’t get a URL that maps to a resource that doesn’t exist. Ie. if this happens it’s because someone is trying to get to something they shouldn’t by editing the URL directly, or because you have a bug in your URL generating code.

Summary, people aren’t using this mechanism for simple flow control. They’re using it because it really shouldn’t happen - which is an appropriate use of exceptions.

PS. Worth adding that some of that reasoning, re exceptional situations, could map to certain authorization use cases. If you just had a single user type, or maybe it’s an API where you’re tightly controlling the resources you’re directing users to. Ie. where no one would sharing URLs, no one innocently stumbling into a place they don’t have access to, etc. I can imagine exceptions making more sense in an app like that.