Skip to content

Releases: expede/exceptional

More raising helpers

28 Dec 12:27
Compare
Choose a tag to compare

Added ensure!/1 and def!/2

ensure is essentially an alias for foo >>> fn x -> x end.()

ensure!([1, 2, 3])
#=> [1, 2, 3]

%ArgumentError{message: "raise me"} |> ensure!
#=> ** (ArgumentError) raise me

def! foo(...) helps define a def foo(...) plus def foo!(...) variant that raises any final exception from foo

defmodule Foo do
  use Exceptional

  def! foo(a), do: a
end

Foo.foo([1, 2, 3])
#=> [1, 2, 3]

Foo.foo(%ArgumentError{message: "raise me"})
#=> %ArgumentError{message: "raise me"}

Foo.foo!([1, 2, 3])
#=> [1, 2, 3]

Foo.foo!(%ArgumentError{message: "raise me"})
#=> ** (ArgumentError) raise me

Extract Phoenix helpers

20 Dec 03:08
Compare
Choose a tag to compare

Phoenix/Exceptional helpers now live here

(Very) simple Phoenix error view helpers

18 Dec 11:31
Compare
Choose a tag to compare

Phoenix Error View Helpers

Automated rendering of error views based on the message field on exception structs.
This is often sufficient for simple JSON cases and pre-release HTML. It is recommended to write custom error views before production to give your users as much detail as possible in the user friendliest way that their content type allows.

# /web/views/error_view.ex
defrender :error, for: 401, do: "Not authorized"
defrender :error, for: 404, only: [:json], do: "Data not found"
defrender :error, for: 422, except: [:html] do: "Unprocessible entity"
defrender :error, for: 500, do: "Server internal error"

# Example JSON Error Response

404 %{error: "Data not found", reason: "Photo deleted"}

# Example HTML Error Response

500 "Server internal error"

1.4.0

18 Dec 08:49
Compare
Choose a tag to compare

A long-requested feature. I'm finally caving and writing it 😜

normalize(42)
#=> 42

normalize(%Enum.OutOfBoundsError{message: "out of bounds error"})
#=> %Enum.OutOfBoundsError{message: "out of bounds error"}

normalize(:error)
#=> %ErlangError{original: nil}

normalize({:error, "boom"})
#=> %ErlangError{original: "boom"}

normalize({:error, {1, 2, 3}})
#=> %ErlangError{original: {1, 2, 3}}

normalize({:error, "boom with stacktrace", ["trace"]})
#=> %ErlangError{original: "boom with stacktrace"}

normalize({:good, "tuple", ["value"]})
#=> {:good, "tuple", ["value"]}

Something similar was suggested back in #2. I still believe that you should pattern match as much as possible, making normalize superfluous... but this pipes well enough in most situations.

This isn't a complete solution for all tagged tuples. In fact, it is emphatically not that. For example:

normalize({:my_special, "tuple", ["value"]})
#=> {:my_special, "tuple", ["value"]}

☝️ You'll still need to handle those cases in pattern matches yourself (unless you do want the tuple value, which is a common case). To do this, you may pass an additional converting function. Example:

{:oh_no, {"something bad happened", %{bad: :thing}}}
|> normalize(fn
  {:oh_no, {message, _}} -> %File.Error{reason: message}) # This case
  {:bang, message        -> %File.CopyError{reason: message})
  otherwise              -> otherwise
end)
#=> %File.Error{message: msg}

{:oh_yes, {1, 2, 3}}
|> normalize(fn
  {:oh_no, {message, _}} -> %File.Error{reason: message})
  {:bang, message        -> %File.CopyError{reason: message})
  otherwise              -> otherwise # This case
end)
#=> {:oh_yes, {1, 2, 3}}

The advantage for all of the common patterns is that we have a single struct representation for pattern matching (unless the error tuple is not one of the most common cases). Hopefully including this function doesn't cause any confusion 😨 Time will tell...

Make dangerous functions safe

18 Dec 04:25
Compare
Choose a tag to compare

safe/1 & safe/2 (plus pun aliases: lower/1 and lower/2 make functions that raise exceptions return those exceptions instead.

Does not catch throws, because that causes all sorts of odd behaviour (REPL errors returned as tuples or (structs if normalized), then they get piped into functions... all sorts of mess).

toothless_fetch = safe(&Enum.fetch!/2)
[1,2,3] |> toothless_fetch.(1)
#=> 2

toothless = safe(&Enum.fetch!/2)
[1,2,3] |> toothless.(999)
#=> %Enum.OutOfBoundsError{message: "out of bounds error"}

safe(&Enum.fetch!/2).([1,2,3], 999)
#=> %Enum.OutOfBoundsError{message: "out of bounds error"}

~~~ operator & more uses

31 Aug 15:07
Compare
Choose a tag to compare

~~~

Added a unary ~~~ operator for converting back to a tagged status.

More uses

# Default
use Exceptional                                                       

#  Only operators                                                                    
use Exceptional, only: :operators                                     

#Only named functions                                                                          
use Exceptional, only: :named_functions                               

# If you like to live extremely dangerously. This is _not recommended_.     
# Please be certain that you want to override the standard lib before using.
use Exceptional, include: :overload_pipe

if_exception

28 Aug 23:45
Compare
Choose a tag to compare

Added if_exception, which has the ability to take the familiar forms:

if_exception [1,2,3], do: fn %{message: msg} -> msg end.(), else: Quark.id

if_exception [1,2,3] do
  fn %{message: msg} -> msg end.()
else
  Quark.id
end

v1.0.4

28 Aug 20:30
Compare
Choose a tag to compare

Minor fixes 🔧

  • Improve README
  • Fix use Exceptional typo

v1.0.0

28 Aug 19:46
Compare
Choose a tag to compare

First full version 🎉🎉🎉

Package allows you to delay raiseing exceptions as long as possible, delegating control to the caller. Also provides a nice alternative to the {:tag, "message"} pattern, with much more possible information, given that Exception can be implemented for any struct.