-
-
Save ityonemo/9f3b8f86a04f2636ab308b1b1a39968e to your computer and use it in GitHub Desktop.
check your schemas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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