defmodule Mix.ExShopifySchema do @moduledoc """ Conveniences for writing ExShopifySchema related Mix tasks. """ import Mix.Generator alias ExShopifySchema.Graphql.Introspection.Definition @typep opts() :: [ dir: Path.t(), namespace: module(), ecto_types: %{(type_name :: String.t()) => {:field | :embeds, module(), Keyword.t()}} ] @spec generate_scalar(Definition.ScalarType.t(), opts()) :: module() def generate_scalar(%Definition.ScalarType{} = type, opts) do namespace = Keyword.fetch!(opts, :namespace) dir = Keyword.fetch!(opts, :dir) mod = Module.concat(namespace, type.name) assigns = [ mod: mod, description: type.description ] content = eval_template(type, assigns) true = create_file(file_path(type, dir), content, force: true) mod end @spec generate_object_like( Definition.ObjectType.t() | Definition.InterfaceType.t() | Definition.InputObjectType.t(), opts() ) :: module() def generate_object_like(type, opts) when is_struct(type, Definition.ObjectType) when is_struct(type, Definition.InterfaceType) when is_struct(type, Definition.InputObjectType) do namespace = Keyword.fetch!(opts, :namespace) dir = Keyword.fetch!(opts, :dir) mod = Module.concat(namespace, type.name) fields = Enum.flat_map(type.fields, fn field -> List.wrap(build_object_field(field, opts)) end) assigns = [ mod: mod, description: type.description, fields: fields ] content = eval_template(type, assigns) true = create_file(file_path(type, dir), content, force: true) mod end defp build_object_field(field, opts) when is_struct(field, Definition.Field) when is_struct(field, Definition.InputValue) do {cardinality, type} = unwrap_object_field_type(field.type) ecto_types = Keyword.fetch!(opts, :ecto_types) case {cardinality, Map.fetch(ecto_types, type.name)} do {:one, {:ok, {:field, type, opts}}} -> {:loadable_field, field_name(field.name), type, opts} {:many, {:ok, {:field, type, opts}}} -> {:loadable_field, field_name(field.name), {:array, type}, opts} {:one, {:ok, {:embeds, type, opts}}} -> {:embeds_one, field_name(field.name), type, opts} {:many, {:ok, {:embeds, type, opts}}} -> {:embeds_many, field_name(field.name), type, opts} {_cardinality, :error} -> nil end end defp unwrap_object_field_type(type, is_list? \\ false) defp unwrap_object_field_type(%Definition.NonNullType{} = type, is_list?), do: unwrap_object_field_type(type.type, is_list?) defp unwrap_object_field_type(%Definition.ListType{} = type, _is_list?), do: unwrap_object_field_type(type.type, true) defp unwrap_object_field_type(%Definition.NamedType{} = type, is_list?), do: if(is_list?, do: {:many, type}, else: {:one, type}) defp field_name(name) when is_binary(name) do name |> Macro.underscore() |> String.to_atom() end defp file_path(%{name: name} = type, dir) when is_struct(type) do type_name = Macro.underscore(name) Path.join(dir, "#{type_name}.ex") end defp eval_template(type, assigns) do type |> template_file_path() |> EEx.eval_file(assigns: assigns) end defp template_file_path(%Definition.ScalarType{} = type) do type_name = Macro.underscore(type.name) Path.join([schema_template_dir(), "scalars", "#{type_name}.eex"]) end defp template_file_path(type) when is_struct(type, Definition.ObjectType) when is_struct(type, Definition.InterfaceType) when is_struct(type, Definition.InputObjectType) do Path.join(schema_template_dir(), "object_like.eex") end defp schema_template_dir do Path.join( :code.priv_dir(:ex_shopify_schema), "graphql/templates/schemas/" ) end end