victorolinasc

victorolinasc

Should `Base.decodeXX` functions return more than just `:error`?

I was about to create an issue on the Elixir repo about the current behaviour of Base.decodeXX functions in core. I’ll first post it here and if people agree I’ll open the issue and send a PR. Just wanted to avoid polluting the issue tracker and getting some feedback beforehand.

Issue description

Base.decodeXX functions return a very unuseful :error only atom. This is fine for all the cases that you trust the encoding. Sometimes, though, we need to define some external “protocol” that will send/receive encoded binaries. In these cases, it is common to have something like:

def some_input_handling_fun(binary) do
  with {:ok, decoded} <- Base.decode64(binary),
         {:ok, result} <- do_something_with_decoded(decoded) do
      {:ok, result}
  end
end

The issue is that the return of decodeXX funs, if it fails, is simply :error which makes debugging this a bit tricky. If anything else returns :error (which it shouldn’t but you know how mankind works, right?) then we will need more context to understand what went wrong.

If this is ok, I can send a PR changing these specs and implementation here, here and here.

This is public API and so, I am not sure if this can be done as it would be backwards incompatible. I think this is a bug since many times it was mentioned that bad error “practices” in the core language should be treated as such. If this is not the case, then I’ll wrap the function locally anyway :slight_smile:

Environment

  • Elixir & Erlang/OTP versions (elixir --version):
elixir --version
Erlang/OTP 21 [erts-10.0.6] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Elixir 1.7.3 (compiled with Erlang/OTP 21)
  • Operating system:
uname -a

Linux 4.17.19-200.fc28.x86_64 #1 SMP Fri Aug 24 15:47:41 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

Current behavior

Not following error tuples for decoding operations.

Expected behavior

Return {:error, :bad_encoding} instead of just :error

First Post!

LostKobrakai

LostKobrakai

To me this feels like you try to change something just because it’s inconvenient with with. That’s imho not a reason to change anything. You’ll have the same issues with not just the Base module’s functions, but with many other functions.

I feel like changing the return types of the Base module might be justified if the additional second error atom would provide more details about the error, but in this case there’s only one error case: Can’t be decoded.

Most Liked

mgwidmann

mgwidmann

I think that because we have the freedom to return whatever we are allowing that to cloud our judgement a bit. In a statically typed language there wouldn’t even be an argument about this because the same type would be returned everywhere. There can most certainly be more information returned even though the actual issue is always the same. Consider this:

case Base.decode64(text) do
  {:ok, data} -> {:ok, data}
  {:error, {:bad_encoding, byte_offset}} -> {:error, "Encountered error decoding after the #{byte_offset}th byte\n#{text}"}
end

Both what went wrong and specifics about the error are accessible in native elixir terms and we can then choose to create a more friendly or debuggable error (or even take different action depending on the error metadata if we so choose).

Supposed we had a type to represent this as part of the standard library:

defmodule Result do
  @type success :: {:ok, any()}
  @type error :: {:error, {atom(), any()}}
  @type t :: success | error
end

This is so common in Erlang and Elixir. Why is there no type for it?

Now would the question really be what should we return? In order to be consistent it would become mandatory to return a Result.t() everywhere, even where not necessary, not only for consistency sake but for backwards compatibility. Now if we ever need to add another error condition, or some metadata about what went wrong, its easy to do so.

alexcastano

alexcastano

I think if you return a tuple is because you’re returning more information about the error. However, {:error, :bad_encoding} is not giving you more information than {:error}. If I would need this for a with, I would create a simple wrapper:

defmodule MyBase do
  def decode64(bin) do
    case Base.decode(bin) do
      :error -> {:error, :bad_encoding}
      any -> any
    end
  end
end
victorolinasc

victorolinasc

The more information, in this case, is that this is an error from a decoding function. We could go even further and provide {:error, :bad_base64_encoding} and so on.

Of course you can wrap the function and that is valid even for functions that DO return error tuples, but my point here is about how you instrospect errors. If you “know” beforehand this return is possible from decode functions, then sure, you can wrap it. But what if you don’t know this and has this log on your production system “Some error”?

So: :error is not as obvious as {:error, :bad_encoding} to my understanding at least. What I was hoping to achieve here is to provide just what you people are saying: being obvious about what happened in an error other than just saying “an error occured”.

In any case, it seems like I am alone in this quest hehehe that’s fine too. I thought this would turn out the other way around but it is not like there is a wrong or right here.

Last Post!

victorolinasc

victorolinasc

That is a very useful pattern indeed! Thanks for remembering to post here. It is a good future reference.

Where Next?

Popular in Discussions Top

CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -&gt; H; nth(N, [_|T]) when N &gt; 1 -&gt; nth(N - 1, T). Elixir Enum.at … coo...
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 40165 209
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
Rustixir
Hi everyone, im working on find best language/framework/system for high concurrency, high performance and stable performance after wor...
New
fireproofsocks
I’ve been working on an Elixir project that has required a lot of scripting. I usually reach for Elixir because I like it more (and in th...
New

Other popular topics Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 40165 209
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

We're in Beta

About us Mission Statement