Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
check your schemas
defmodule SchemaCheck do
defstruct state: :pre, lines: []
def __after_compile__(%{module: module}, _binary) do
puml_file_path =
:attributes
|> module.__info__()
|> Keyword.get(:external_resource)
|> Enum.filter(&(Path.extname(&1) == ".puml"))
|> List.first()
header_line = "entity #{inspect(module)} {"
puml_table =
puml_file_path
|> File.read!()
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.reduce(%__MODULE__{}, &find_table(&1, &2, header_line))
|> Map.get(:lines)
|> Enum.reverse()
|> case do
[] ->
raise CompileError,
description: "table for #{inspect(module)} not found in #{puml_file_path}"
list ->
Enum.into(list, %{})
end
compare(puml_table, module, puml_file_path)
end
defp find_table(line, %{state: :pre} = state, header_line) do
if String.contains?(line, header_line),
do: %{state | state: :table},
else: state
end
defp find_table(line, %{state: :table} = state, _) do
cond do
String.contains?(line, "}") ->
%{state | state: :done}
String.contains?(line, "--") ->
state
String.contains?(line, "<") ->
state
true ->
kv =
line
|> String.trim_leading("*")
|> String.split(":")
|> Enum.map(&String.trim/1)
|> List.to_tuple()
if tuple_size(kv) == 2 do
%{state | lines: [kv | state.lines]}
else
state
end
end
end
defp find_table(_, state, _), do: state
defp compare(puml_table, module, puml_file_path) do
atom_fields = module.__schema__(:fields)
fields =
for field <- atom_fields, into: %{} do
{Atom.to_string(field), field}
end
# first make sure that everything in the puml table is in the changeset
Enum.each(puml_table, fn {field, type_str} ->
type = module.__schema__(:type, fields[field])
cond do
field not in Map.keys(fields) ->
raise CompileError,
description:
"table for #{inspect(module)} is missing field \"#{field}\" in #{puml_file_path}"
type_mismatches?(type, type_str) ->
raise CompileError,
description:
"table for #{inspect(module)} field \"#{field}\" type \"#{inspect(type)}\" mismatches \"#{
type_str
}\" \"#{puml_file_path}\""
true ->
:ok
end
end)
# next make sure that everything in the changeset is in the puml tablea
:fields
|> module.__schema__()
|> Kernel.--([:inserted_at, :updated_at, :id])
|> Enum.each(fn key ->
Atom.to_string(key) in Map.keys(puml_table) or
raise CompileError,
description:
"table for #{inspect(module)} field #{key} missing from \"#{puml_file_path}\""
end)
end
defp type_mismatches?(:string, "text"), do: false
defp type_mismatches?(:id, "fk_" <> _), do: false
defp type_mismatches?({:parameterized, _, %{related: type}}, type_str) do
inspect(type) != type_str
end
defp type_mismatches?({:embed, %{related: related}}, type_str) do
inspect(related) != type_str
end
defp type_mismatches?(type, type_str) do
inspect(type) != type_str and Atom.to_string(type) != type_str
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment