File size: 3,351 Bytes
936b397 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | defmodule Plausible.Audit.EncoderError do
defexception [:message]
end
defprotocol Plausible.Audit.Encoder do
def encode(x)
def encode(x, opts)
end
defimpl Plausible.Audit.Encoder, for: Ecto.Changeset do
def encode(changeset, opts \\ []) do
changes =
Enum.reduce(changeset.changes, %{}, fn {k, v}, acc ->
Map.put(acc, k, Plausible.Audit.Encoder.encode(v, opts))
end)
data = Plausible.Audit.Encoder.encode(changeset.data, opts)
case {map_size(data), map_size(changes)} do
{n, 0} when n > 0 ->
data
{0, n} when n > 0 ->
changes
{0, 0} ->
%{}
_ ->
%{before: data, after: changes}
end
end
end
defimpl Plausible.Audit.Encoder, for: Map do
def encode(x, opts \\ []) do
{allow_not_loaded, data} = Map.pop(x, :__allow_not_loaded__)
raise_on_not_loaded? = Keyword.get(opts, :raise_on_not_loaded?, true)
Enum.reduce(data, %{}, fn
{k, %Ecto.Association.NotLoaded{}}, acc ->
if k in allow_not_loaded or not raise_on_not_loaded? do
acc
else
raise Plausible.Audit.EncoderError,
message:
"#{k} association not loaded. Either preload, exclude or mark it as :allow_not_loaded in #{__MODULE__} options"
end
{k, v}, acc ->
Map.put(acc, k, Plausible.Audit.Encoder.encode(v, opts))
end)
end
end
defimpl Plausible.Audit.Encoder, for: [Integer, BitString, Float] do
def encode(x, _opts \\ []), do: x
end
defimpl Plausible.Audit.Encoder, for: [DateTime, Date, NaiveDateTime, Time] do
def encode(x, _opts \\ []), do: to_string(x)
end
defimpl Plausible.Audit.Encoder, for: Atom do
def encode(x, opts \\ [])
def encode(nil, _opts), do: nil
def encode(true, _opts), do: true
def encode(false, _opts), do: false
def encode(x, _opts), do: Atom.to_string(x)
end
defimpl Plausible.Audit.Encoder, for: List do
def encode(x, opts \\ []) do
Enum.map(x, &Plausible.Audit.Encoder.encode(&1, opts))
end
end
defimpl Plausible.Audit.Encoder, for: Any do
defmacro __deriving__(module, struct, options) do
deriving(module, struct, options)
end
def deriving(module, _struct, options) do
only = options[:only]
except = options[:except]
allow_not_loaded = options[:allow_not_loaded] || []
extractor =
cond do
only ->
quote(
do:
struct
|> Map.take(unquote(only))
|> Map.put(:__allow_not_loaded__, unquote(allow_not_loaded))
)
except ->
except = [:__struct__ | except]
quote(
do:
struct
|> Map.drop(
unquote(except)
|> Map.put(:__allow_not_loaded__, unquote(allow_not_loaded))
)
)
true ->
quote(
do:
struct
|> Map.delete(:__struct__)
|> Map.put(:__allow_not_loaded__, unquote(allow_not_loaded))
)
end
quote do
defimpl Plausible.Audit.Encoder, for: unquote(module) do
def encode(struct, opts \\ []) do
Plausible.Audit.Encoder.encode(unquote(extractor), opts)
end
end
end
end
def encode(_), do: raise("Implement me")
def encode(_, _), do: raise("Implement me")
end
|