defmodule EctoSparkles.ErlangTermBinary do
@moduledoc """
A custom Ecto type for handling the serialization of arbitrary
data types stored as binary data in the database. Requires the
underlying DB field to be a binary.
"""
use Ecto.Type
import Untangle
def type, do: :binary
@doc """
Provides custom casting rules for params. Nothing changes here.
We only need to handle deserialization.
"""
def cast(:any, term), do: {:ok, term}
def cast(term), do: {:ok, term}
@doc """
Convert the raw binary value from the database back to
the desired term.
Uses `Plug.Crypto.non_executable_binary_to_term/2` - a restricted version of `:erlang.binary_to_term/2` that forbids executable terms, such as anonymous functions.
By default this function does not restrict atoms, except if compiled in prod env, then the [:safe] option is set, so only existing (and loaded) atoms will be deserialized.
"""
if Application.compile_env!(:ecto_sparkles, :env)==:prod do
IO.puts("EctoSparkles.ErlangTermBinary: will be used in safe mode")
def load(raw_binary) when is_binary(raw_binary) do
{:ok, Plug.Crypto.non_executable_binary_to_term(raw_binary, [:safe])}
rescue
e in ArgumentError ->
# FIXME: find another approach
error(e, "!!! Could not deserialize term from DB, falling back to unsafe")
{:ok, Plug.Crypto.non_executable_binary_to_term(raw_binary) |> info()}
end
else
IO.puts("EctoSparkles.ErlangTermBinary: will be used in unsafe mode")
def load(raw_binary) when is_binary(raw_binary),
do: {:ok, Plug.Crypto.non_executable_binary_to_term(raw_binary)}
end
@doc """
Converting the data structure to binary for storage.
"""
def dump(term), do: {:ok, :erlang.term_to_binary(term)}
end