defmodule Inspecto.Schema do
@moduledoc """
This defines the shape of Ecto schema inspections.
"""
alias Inspecto.Schema.Field
@type t :: %__MODULE__{
module: module(),
source: String.t() | nil,
primary_key: [atom()],
fields: [Field.t()]
}
defstruct module: nil, source: nil, primary_key: [], fields: []
defmodule Field do
@moduledoc """
Contains info about a specific field.
"""
@type t :: %__MODULE__{
name: atom(),
type: atom() | tuple(),
default: any()
}
defstruct name: nil, type: nil, default: nil
end
@doc """
Inspects the given Ecto schema module. This will return an `:invalid_module` tuple
if the given module does not define an Ecto schema (this isn't 100% accurate, but
it's unlikely that other modules would coincidentally define the same functions
as an Ecto schema).
## Examples
iex> Inspecto.Schema.inspect(MyApp.Schemas.MyThing)
{:ok, %Inspecto.Schema{
module: MyApp.Schemas.MyThing,
source: "things",
primary_key: [:id],
fields: [
%Inspecto.Schema.Field{name: :id, type: :integer, default: nil},
%Inspecto.Schema.Field{name: :name, type: :string, default: ""},
%Inspecto.Schema.Field{name: :created_at, type: :naive_datetime, default: nil},
]
}}
iex> Inspecto.Schema.inspect(Enum)
{:invalid_module, Enum}
"""
@spec inspect(module :: module()) :: {:ok, t()} | {:invalid_module, module()}
def inspect(module) when is_atom(module) do
{:ok,
%__MODULE__{
module: module,
source: module.__schema__(:source),
primary_key: module.__schema__(:primary_key),
fields:
module.__struct__()
|> Map.from_struct()
|> Map.delete(:__meta__)
|> Enum.map(fn {fieldname, default} ->
%Field{name: fieldname, type: fieldtype(module, fieldname), default: default}
end)
}}
rescue
UndefinedFunctionError -> {:invalid_module, module}
end
@spec fieldtype(module :: module(), fieldname :: atom()) :: any()
defp fieldtype(module, fieldname) do
module.__changeset__()
|> Map.get(fieldname)
|> case do
{:embed, %{cardinality: :many, related: related}} ->
"ARRAY(#{stringify(related)})"
other ->
other
end
end
defp stringify(module), do: String.trim_leading("#{module}", "Elixir.")
end