Current section

Files

Jump to
graphql_query lib mix tasks graphql_query.schema.fetch.ex
Raw

lib/mix/tasks/graphql_query.schema.fetch.ex

defmodule Mix.Tasks.GraphqlQuery.Schema.Fetch do
@shortdoc "Fetches remote GraphQL schemas and saves them locally"
@moduledoc """
Fetches remote GraphQL schemas and saves them to the local filesystem.
Discovers all modules that use `GraphqlQuery.Schema` with a `:remote` configuration
and downloads their schemas from the configured URLs.
## Usage
# Fetch all remote schemas
mix graphql_query.schema.fetch
# Fetch a specific schema module
mix graphql_query.schema.fetch MyApp.ExternalSchema
## Configuration
Schema modules must be configured with the `:remote` option:
defmodule MyApp.ExternalSchema do
use GraphqlQuery.Schema,
remote: [url: "https://api.example.com/schema.graphql"]
end
For APIs that only support introspection (no direct SDL endpoint):
defmodule MyApp.IntrospectedSchema do
use GraphqlQuery.Schema,
remote: [url: "https://api.example.com/graphql", mode: :introspect]
end
The schemas directory can be configured at multiple levels:
1. Per-module `:schemas_dir` option in `use GraphqlQuery.Schema`
2. Application config: `config :graphql_query, schemas_dir: "priv/graphql/schemas"`
3. Default: `"priv/graphql/schemas"`
"""
use Mix.Task
alias GraphqlQuery.Schema.Remote
@impl Mix.Task
def run(args) do
Mix.Task.run("compile", [])
Mix.Task.run("app.start", [])
module_filter = parse_module_filter(args)
schemas = Remote.discover_remote_schemas()
schemas =
if module_filter do
Enum.filter(schemas, fn info -> info.module == module_filter end)
else
schemas
end
if Enum.empty?(schemas) do
if module_filter do
Mix.shell().error(
"No remote schema found for module #{inspect(module_filter)}. " <>
"Make sure the module uses `use GraphqlQuery.Schema, remote: [url: \"...\"]`."
)
else
Mix.shell().info("No remote schema modules found.")
end
else
Mix.shell().info("Found #{length(schemas)} remote schema(s) to fetch.\n")
results = Enum.map(schemas, &fetch_and_save/1)
successes = Enum.count(results, &(&1 == :ok))
failures = length(results) - successes
Mix.shell().info("")
if failures > 0 do
Mix.raise("Schema fetch failed: #{successes} succeeded, #{failures} failed.")
else
Mix.shell().info("All #{successes} schema(s) fetched successfully.")
end
end
end
defp fetch_and_save(info) do
%{module: module, remote: remote, schema_path: schema_path} = info
Mix.shell().info(
"Fetching schema for #{inspect(module)} " <>
"from #{format_url(Keyword.fetch!(remote, :url))} " <>
"(#{Keyword.get(remote, :mode, :fetch)})..."
)
case Remote.fetch_schema(info) do
{:ok, content} ->
case Remote.save_schema(schema_path, content) do
:ok ->
Mix.shell().info(" ✓ Saved to #{schema_path}")
:ok
{:error, reason} ->
Mix.shell().error(" ✗ Failed to save to #{schema_path}: #{inspect(reason)}")
:error
end
{:error, reason} ->
Mix.shell().error(" ✗ Failed to fetch: #{reason}")
:error
end
end
# Format URL for display: show plain strings as-is, MFA tuples as Module.fun/0.
defp format_url(url) when is_binary(url), do: url
defp format_url({mod, fun}), do: "#{inspect(mod)}.#{fun}/0"
defp parse_module_filter([]), do: nil
defp parse_module_filter([module_string | _]) do
Module.concat([module_string])
end
end