The exceptional library and the returning-exception format was designed for easy piping and great compatibility with Plug/Phoenix (remember that your custom exception can give its own error and status code with Plug/Phoenix, making it nice to propagate them up). On its own it is not really much better or worse then, say, tagged tuples (well, it is better in Phoenix regardless), but piping tagged tuples is far more difficult (especially considering the myriad of formats). Your above example, replicated here:
If done in the exceptional āstyleā (with full importing, which is not default, but I like it), while not changing the exposed API (which if you did would make it even more clean), would become something like this:
request_ip
|> get_client_ip(params)
|> Geoip.lookup_ip() |> normalize # normalize takes a tagged-tuple and converts it to an ErlangError or unwraps an :ok tuple
|> bool_test(should_search?/1).(parse_exclusion_zone(params)) # This is proposed in a PR right now to wrap true/false tests, passes original value if true, else returns exception
|> (&build_search_query(%SearchQuery{}, &1, params)).() # Why does your build_search_query take `location` as its second instead of first arg?
|> NGSearch.search() |> normalize
|> Analytics.track(client_ip, "api", "search_results")
|> (&json(conn, &1)).()
|> ensure! # This means if any exception was returned at any prior point, then raise it instead
And since I raised the exception at the end if any existed then the exceptions are handled as normal via phoenix, and you can decorate them and return them with status codes and add handlers in your error handler to handle errors properly (which āthatā is where your json error handling should be, not in your controller in my opinion), and of course they will get logged as normal exceptions do, etcā¦
The API could of course be cleaned up a lot by re-orienting arguments and such:
request_ip
|> get_client_ip(params)
|> Geoip.lookup_ip() |> normalize # normalize takes a tagged-tuple and converts it to an ErlangError or unwraps an :ok tuple
|> should_search(parse_exclusion_zone(params))
|> build_search_query(%SearchQuery{}, params)
|> NGSearch.search() |> normalize
|> Analytics.track(client_ip, "api", "search_results")
|> (&json(conn, &1)).()
|> ensure!
Or something like that, which can always be cleaned up further too (and using other libraries like tap or so you can put arguments in non-first positions in a pipe-chain as well).
Using normal use Exceptional without other options, the above last chain would look like:
request_ip
|> get_client_ip(params)
|> Geoip.lookup_ip() |> normalize # normalize takes a tagged-tuple and converts it to an ErlangError or unwraps an :ok tuple
~> should_search(parse_exclusion_zone(params))
~> build_search_query(%SearchQuery{}, params)
~> NGSearch.search() |> normalize
~> Analytics.track(client_ip, "api", "search_results")
~> (&json(conn, &1)).()
|> ensure!























