sodapopcan
Compile time checking struct keys for a protocol
I have a protocol that is used on Ecto Schemas. The protocol requires that two fields are defined with specific defaults.
I have come up with a working solution that I’m pretty happy with, but I’m just looking for feedback as I’m unsure if this is the optimal way (and maybe I’ve looked past a much simpler solution). In short, I used __after_compile__ and Ecto’s reflection functions to ensure the keys are there.
Obviously one drawback of this is that it must be @derived though I’m not so worried about that.
I’m also wondering about the line %{context_modules: [module]} <- env and if that could have any ramifications. Could there ever be more than one context module in this scenario?
Here is the code:
defmodule MyApp.MyProtocolError do
defexception [:message]
end
defmodule MyApp.MyProtocolCompile do
@required_fields [
name: %{type: :string},
checked: %{type: :boolean, default: true}
]
def __after_compile__(env, _bytecode) do
with %{context_modules: [module]} <- env do
messages =
for {field, opts} <- required_fields, reduce: [] do
messages ->
if module.__schema__(:virtual_type, field) != opts.type do
message =
" * #{__MODULE__} must have a field `:#{field}` of type `:#{opts.type}`"
message =
message <>
((Map.has_key?(opts, :default) && " and default to `#{opts.default}`") || "")
[message | messages]
else
messages
end
end
if messages != [] do
raise MyApp.MyProtocolError,
message: "\n" <> Enum.join(messages, "\n")
end
end
end
end
defprotocol MyApp.MyProtocol do
def do_it(module)
@impl true
defmacro __deriving__(module, options) do
quote location: :keep do
@after_compile MyApp.MyProtocolCompile
defimpl MyApp.MyProtocol, for: unquote(module) do
def do_it(_module) do
unquote(options).key
end
end
end
end
end
The other thing I did was put the __after_compile__ in the __deriving__ so I could just unquote(module) to get the module, but I like this way a bit better.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









