Packages
ash_graphql
1.7.13
1.9.4
1.9.3
1.9.2
1.9.1
1.9.0
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.0
1.5.1
1.5.0
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.5
1.0.0-rc.4
retired
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.28.1
0.28.0
0.27.1
0.27.0
0.26.9
0.26.8
0.26.7
0.26.6
0.26.5
0.26.4
0.26.3
0.26.2
0.26.0
0.25.13
0.25.12
0.25.10
0.25.9
0.25.8
0.25.7
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.13
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.0
retired
0.20.5
0.20.4
0.20.3
0.20.2
0.20.1
0.20.0-rc.3
0.20.0-rc.2
0.20.0-rc.1
0.20.0-rc.0
0.19.0
0.18.0-rc0
0.17.5
0.17.5-rc0
0.17.4
0.17.2
0.17.1
0.17.0
0.16.28
0.16.27
0.16.26
0.16.25
0.16.24
0.16.23
0.16.22
0.16.21
0.16.20
0.16.18-rc5
0.16.18-rc4
0.16.18-rc3
0.16.18-rc2
0.16.18-rc1
0.16.18-rc0
0.16.17
0.16.16
0.16.15
0.16.14
0.16.13
0.16.12
0.16.11
0.16.10
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.5
0.12.4
0.12.3
0.12.1
0.12.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
0.1.2
The extension for building GraphQL APIs with Ash
Current section
Files
Jump to
Current section
Files
lib/igniter.ex
if Code.ensure_loaded?(Igniter) do
defmodule AshGraphql.Igniter do
@moduledoc "Codemods and utilities for working with AshGraphql & Igniter"
@doc "Returns the AshGraphql schema containing the domain in question, or a list of all AshGraphql schemas"
def find_schema(igniter, domain) do
{igniter, modules} = ash_graphql_schemas(igniter)
modules
|> Enum.find(fn module ->
with {:ok, {_igniter, _source, zipper}} <-
Igniter.Project.Module.find_module(igniter, module),
{:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, AshGraphql),
{:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1),
{:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :domains),
{:ok, _zipper} <-
Igniter.Code.List.move_to_list_item(
zipper,
&Igniter.Code.Common.nodes_equal?(&1, domain)
) do
true
else
_ ->
false
end
end)
|> case do
nil ->
{:error, igniter, modules}
module ->
{:ok, igniter, module}
end
end
@doc "Sets up an absinthe schema for AshGraphql"
def setup_absinthe_schema(igniter, schema_name \\ nil) do
schema_name = schema_name || Igniter.Project.Module.module_name(igniter, "GraphqlSchema")
{igniter, domains} = Ash.Domain.Igniter.list_domains(igniter)
{igniter, domains} =
Enum.reduce(domains, {igniter, []}, fn domain, {igniter, list} ->
case Spark.Igniter.has_extension(
igniter,
domain,
Ash.Domain,
:extensions,
AshGraphql.Domain
) do
{igniter, true} -> {igniter, [domain | list]}
{igniter, false} -> {igniter, list}
end
end)
{igniter, resources} = Ash.Resource.Igniter.list_resources(igniter)
{igniter, any_queries?} =
Enum.reduce_while(
Enum.map(resources, &{&1, [2, 3]}) ++ Enum.map(domains, &{&1, [3, 4]}),
{igniter, false},
fn {mod, arities}, {igniter, false} ->
with {:ok, {igniter, _source, zipper}} <-
Igniter.Project.Module.find_module(igniter, mod),
{:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:graphql,
1
),
{:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper),
{:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:queries,
1
),
{:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper) do
has_query? =
Enum.any?([:get, :read_one, :list, :action], fn query_name ->
match?(
{:ok, _},
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
query_name,
arities
)
)
end)
if has_query? do
{:halt, {igniter, true}}
else
{:cont, {igniter, false}}
end
else
_ ->
{:cont, {igniter, false}}
end
end
)
placeholder_query =
unless any_queries? do
~S'''
@desc """
Hello! This is a sample query to verify that AshGraphql has been set up correctly.
Remove me once you have a query of your own!
"""
field :say_hello, :string do
resolve fn _, _, _ ->
{:ok, "Hello from AshGraphql!"}
end
end
'''
end
igniter
|> Igniter.Project.Module.find_and_update_or_create_module(
schema_name,
"""
use Absinthe.Schema
use AshGraphql,
domains: #{inspect(domains)}
import_types Absinthe.Plug.Types
query do
# Custom Absinthe queries can be placed here
#{placeholder_query}
end
mutation do
# Custom Absinthe mutations can be placed here
end
subscription do
# Custom Absinthe subscriptions can be placed here
end
""",
fn zipper ->
# Should never get here
{:ok, zipper}
end
)
end
@doc "Sets up the phoenix module for AshGraphql"
def setup_phoenix(igniter, schema_name \\ nil, socket_name \\ nil, opts \\ []) do
schema_name = schema_name || Igniter.Project.Module.module_name(igniter, "GraphqlSchema")
socket_name = socket_name || Igniter.Project.Module.module_name(igniter, "GraphqlSocket")
case Igniter.Libs.Phoenix.select_router(igniter) do
{igniter, nil} ->
igniter
|> Igniter.add_warning("""
No Phoenix router found, skipping Phoenix installation.
See the Getting Started guide for instructions on installing AshGraphql with `plug`.
If you have yet to set up Phoenix, you'll have to do that manually and then rerun this installer.
""")
{igniter, router} ->
igniter
|> Igniter.Project.Deps.add_dep({:absinthe_phoenix, "~> 2.0"})
|> then(fn igniter ->
if igniter.assigns[:test_mode?] do
igniter
else
Igniter.apply_and_fetch_dependencies(igniter,
error_on_abort?: true,
yes: opts[:yes],
yes_to_deps: true
)
end
end)
|> create_socket(schema_name, socket_name)
|> update_router(router, socket_name, schema_name)
|> update_application(router)
end
end
defp update_router(igniter, router, socket_name, schema_name) do
igniter
|> update_endpoints(router, socket_name)
|> Igniter.Libs.Phoenix.add_pipeline(:graphql, "plug AshGraphql.Plug", router: router)
|> Igniter.Libs.Phoenix.add_scope(
"/gql",
"""
pipe_through [:graphql]
forward "/playground",
Absinthe.Plug.GraphiQL,
schema: Module.concat(["#{inspect(schema_name)}"]),
socket: Module.concat(["#{inspect(socket_name)}"]),
interface: :simple
forward "/",
Absinthe.Plug,
schema: Module.concat(["#{inspect(schema_name)}"])
""",
router: router
)
end
defp update_application(igniter, router) do
{igniter, endpoints} =
Igniter.Libs.Phoenix.endpoints_for_router(igniter, router)
igniter =
Enum.reduce(endpoints, igniter, fn endpoint, igniter ->
igniter
|> Igniter.Project.Application.add_new_child({Absinthe.Subscription, endpoint},
after: endpoint
)
end)
igniter
|> Igniter.Project.Application.add_new_child(AshGraphql.Subscription.Batcher,
after: Absinthe.Subscription
)
end
defp create_socket(igniter, schema_name, socket_name) do
igniter
|> Igniter.Project.Module.find_and_update_or_create_module(
socket_name,
"""
use Phoenix.Socket
use Absinthe.Phoenix.Socket,
schema: #{inspect(schema_name)}
@impl true
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
@impl true
def id(_socket), do: nil
""",
fn zipper ->
# Should never get here
{:ok, zipper}
end
)
end
@doc "Returns all modules that `use AshGraphql`"
def ash_graphql_schemas(igniter) do
Igniter.Project.Module.find_all_matching_modules(igniter, fn _name, zipper ->
match?({:ok, _}, Igniter.Code.Module.move_to_use(zipper, AshGraphql))
end)
end
defp update_endpoints(igniter, router, socket_name) do
{igniter, endpoints_that_need_parser} =
Igniter.Libs.Phoenix.endpoints_for_router(igniter, router)
igniter =
Enum.reduce(endpoints_that_need_parser, igniter, fn endpoint, igniter ->
igniter
|> Igniter.Project.Module.find_and_update_module!(endpoint, fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, Phoenix.Endpoint) do
{:ok, Igniter.Code.Common.add_code(zipper, "use Absinthe.Phoenix.Endpoint")}
end
end)
|> Igniter.Project.Module.find_and_update_module!(endpoint, fn zipper ->
case Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:plug,
2,
&Igniter.Code.Function.argument_equals?(&1, 0, Plug.Parsers)
) do
{:ok, zipper} ->
with {:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1),
{:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :parsers),
{:ok, zipper} <-
Igniter.Code.List.append_new_to_list(zipper, Absinthe.Plug.Parser) do
{:ok, zipper}
else
_ ->
{:warning,
"Could not add `Absinthe.Plug.Parser` to parsers in endpoint #{endpoint}. Please make this change manually."}
end
:error ->
case parser_location(zipper) do
{:ok, zipper} ->
{:ok,
Igniter.Code.Common.add_code(zipper, """
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json, Absinthe.Plug.Parser],
pass: ["*/*"],
json_decoder: Jason
""")}
_ ->
{:warning,
"Could not add `Absinthe.Plug.Parser` to parsers in endpoint #{endpoint}. Please make this change manually."}
end
end
end)
end)
Enum.reduce(endpoints_that_need_parser, igniter, fn endpoint, igniter ->
Igniter.Project.Module.find_and_update_module!(igniter, endpoint, fn zipper ->
case Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:socket,
3,
&Igniter.Code.Function.argument_equals?(&1, 1, socket_name)
) do
{:ok, zipper} ->
# Already installed
{:ok, zipper}
:error ->
case socket_location(zipper) do
{:ok, zipper} ->
{:ok,
Igniter.Code.Common.add_code(zipper, """
socket "/ws/gql", #{inspect(socket_name)},
websocket: true,
longpoll: true
""")}
_ ->
{:warning,
"Could not add #{socket_name} in endpoint #{endpoint}. Please make this change manually."}
end
end
end)
end)
end
defp socket_location(zipper) do
with :error <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:socket,
3,
&Igniter.Code.Function.argument_equals?(&1, 1, Phoenix.LiveView.Socket)
) do
Igniter.Code.Module.move_to_use(zipper, Phoenix.Endpoint)
end
end
defp parser_location(zipper) do
with :error <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:plug,
[1, 2],
&Igniter.Code.Function.argument_equals?(&1, 0, Plug.Telemetry)
),
:error <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:plug,
[1, 2]
) do
Igniter.Code.Module.move_to_use(zipper, Phoenix.Endpoint)
end
end
end
end