defmodule BubbleLib.DslStruct do
@moduledoc """
A "DSL Struct" is a struct which can be exposed in Bubblescript.
Most notable are intent, message, attachment, location, event.
"""
defmacro __using__(struct) do
str_fields = struct |> Keyword.keys() |> Enum.map(&to_string/1)
quote do
defstruct unquote(struct)
@str_fields unquote(str_fields)
@behaviour Access
@impl Access
def fetch(term, key) when key in unquote(str_fields) do
fetch(term, String.to_atom(key))
end
def fetch(term, key) do
Map.fetch(term, key)
end
defoverridable fetch: 2
@impl Access
def get_and_update(term, key, fun) when key in unquote(str_fields) do
get_and_update(term, String.to_atom(key), fun)
end
def get_and_update(term, key, fun), do: Map.get_and_update(term, key, fun)
defoverridable get_and_update: 3
@impl Access
def pop(term, key) when key in unquote(str_fields) do
pop(term, String.to_atom(key))
end
def pop(term, key), do: Map.pop(term, key)
defoverridable pop: 2
def __jason_encode__(struct, opts, only) do
struct
|> Map.keys()
|> Enum.reject(&(&1 != "__struct__" && (is_list(only) && &1 in only)))
|> Enum.map(fn k -> {to_string(k), Map.get(struct, k)} end)
|> Map.new()
|> Jason.Encode.map(opts)
end
end
end
defmacro jason_derive(mod, only \\ nil) do
quote do
defimpl Jason.Encoder, for: unquote(mod) do
def encode(struct, opts) do
unquote(mod).__jason_encode__(struct, opts, unquote(only))
end
end
end
end
def instantiate_structs(%{"__struct__" => mod} = struct) do
struct =
struct
|> Enum.map(fn {k, v} ->
{k, instantiate_structs(v)}
end)
|> Map.new()
mod = String.to_atom(mod)
orig = apply(mod, :__struct__, [])
Map.keys(orig)
|> Enum.map(fn k -> {k, Map.get(struct, to_string(k)) || Map.get(orig, k)} end)
|> Map.new()
|> Map.put(:__struct__, mod)
end
def instantiate_structs(%{__struct__: _} = struct) do
struct
end
def instantiate_structs(%{} = map) do
map
|> Enum.map(fn {k, v} ->
{k, instantiate_structs(v)}
end)
|> Map.new()
end
def instantiate_structs(list) when is_list(list) do
Enum.map(list, &instantiate_structs/1)
end
def instantiate_structs(value), do: value
def struct_from_map(struct, input) do
Enum.reduce(Map.to_list(struct), struct, fn {k, _}, acc ->
case Map.fetch(input, Atom.to_string(k)) do
{:ok, v} -> %{acc | k => v}
:error -> acc
end
end)
end
end