diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 06ee868ff7..7f83808caf 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -18,7 +18,6 @@ jobs: - "1.16" - "1.17" - "1.18" - - "1.19" otp: - "25" - "26" @@ -26,8 +25,6 @@ jobs: - "28" # see https://hexdocs.pm/elixir/compatibility-and-deprecations.html#between-elixir-and-erlang-otp exclude: - - elixir: 1.19 - otp: 25 - elixir: 1.17 otp: 28 - elixir: 1.16 diff --git a/.gitignore b/.gitignore index 814da1a8fb..80560a3d47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.claude +.vscode /bench /_build /cover @@ -7,7 +9,6 @@ erl_crash.dump *.ez src/*.erl -.tool-versions* missing_rules.rb .DS_Store /priv/plts/*.plt diff --git a/lib/absinthe/phase/debug.ex b/lib/absinthe/phase/debug.ex index 59bb374b7f..296fd9ef7c 100644 --- a/lib/absinthe/phase/debug.ex +++ b/lib/absinthe/phase/debug.ex @@ -8,7 +8,7 @@ defmodule Absinthe.Phase.Debug do @spec run(any, Keyword.t()) :: {:ok, Blueprint.t()} def run(input, _options \\ []) do if System.get_env("DEBUG") do - IO.inspect(input, label: :debug_blueprint_output) + IO.inspect(input, label: "debug_blueprint_output") end {:ok, input} diff --git a/lib/absinthe/schema/notation/sdl_render.ex b/lib/absinthe/schema/notation/sdl_render.ex index 29770ccf31..03175971ae 100644 --- a/lib/absinthe/schema/notation/sdl_render.ex +++ b/lib/absinthe/schema/notation/sdl_render.ex @@ -7,9 +7,11 @@ defmodule Absinthe.Schema.Notation.SDL.Render do @line_width 120 - def inspect(term, %{pretty: true}) do + def inspect(term, %{pretty: true} = options) do + adapter = Map.get(options, :adapter, Absinthe.Adapter.LanguageConventions) + term - |> render() + |> render([], adapter) |> concat(line()) |> format(@line_width) |> to_string @@ -25,9 +27,9 @@ defmodule Absinthe.Schema.Notation.SDL.Render do Absinthe.Type.BuiltIns.Scalars, Absinthe.Type.BuiltIns.Introspection ] - defp render(bp, type_definitions \\ []) - defp render(%Blueprint{} = bp, _) do + # 3-arity render functions (with adapter) + defp render(%Blueprint{} = bp, _, adapter) do %{ schema_definitions: [ %Blueprint.Schema.SchemaDefinition{ @@ -48,11 +50,32 @@ defmodule Absinthe.Schema.Notation.SDL.Render do |> Enum.filter(& &1.__private__[:__absinthe_referenced__]) ([schema_declaration] ++ directive_definitions ++ types_to_render) - |> Enum.map(&render(&1, type_definitions)) + |> Enum.map(&render(&1, type_definitions, adapter)) |> Enum.reject(&(&1 == empty())) |> join([line(), line()]) end + defp render(%Blueprint.Schema.DirectiveDefinition{} = directive, type_definitions, adapter) do + locations = directive.locations |> Enum.map(&String.upcase(to_string(&1))) + + concat([ + "directive ", + "@", + string(adapter.to_external_name(directive.name, :directive)), + arguments(directive.arguments, type_definitions), + repeatable(directive.repeatable), + " on ", + join(locations, " | ") + ]) + |> description(directive.description) + end + + # Catch-all 3-arity render - just ignores adapter and delegates to 2-arity + defp render(term, type_definitions, _adapter) do + render(term, type_definitions) + end + + # 2-arity render functions for all types defp render(%Blueprint.Schema.SchemaDeclaration{} = schema, type_definitions) do block( concat([ @@ -185,21 +208,7 @@ defmodule Absinthe.Schema.Notation.SDL.Render do |> description(scalar_type.description) end - defp render(%Blueprint.Schema.DirectiveDefinition{} = directive, type_definitions) do - locations = directive.locations |> Enum.map(&String.upcase(to_string(&1))) - - concat([ - "directive ", - "@", - string(directive.name), - arguments(directive.arguments, type_definitions), - repeatable(directive.repeatable), - " on ", - join(locations, " | ") - ]) - |> description(directive.description) - end - + # 2-arity render functions defp render(%Blueprint.Directive{} = directive, type_definitions) do concat([ " @", @@ -250,21 +259,36 @@ defmodule Absinthe.Schema.Notation.SDL.Render do render(%Blueprint.TypeReference.Identifier{id: identifier}, type_definitions) end + # General catch-all for 2-arity render - delegates to 3-arity with default adapter + defp render(term, type_definitions) do + render(term, type_definitions, Absinthe.Adapter.LanguageConventions) + end + # SDL Syntax Helpers - defp directives([], _) do + # 3-arity directives functions + defp directives([], _, _) do empty() end - defp directives(directives, type_definitions) do + defp directives(directives, type_definitions, adapter) do directives = Enum.map(directives, fn directive -> - %{directive | name: Absinthe.Utils.camelize(directive.name, lower: true)} + %{directive | name: adapter.to_external_name(directive.name, :directive)} end) concat(Enum.map(directives, &render(&1, type_definitions))) end + # 2-arity directives functions + defp directives([], _) do + empty() + end + + defp directives(directives, type_definitions) do + directives(directives, type_definitions, Absinthe.Adapter.LanguageConventions) + end + defp directive_arguments([], _) do empty() end diff --git a/lib/absinthe/type/field.ex b/lib/absinthe/type/field.ex index aac93cc6ef..fdce088b9e 100644 --- a/lib/absinthe/type/field.ex +++ b/lib/absinthe/type/field.ex @@ -75,7 +75,9 @@ defmodule Absinthe.Type.Field do * `:name` - The name of the field, usually assigned automatically by the `Absinthe.Schema.Notation.field/4`. Including this option will bypass the snake_case to camelCase conversion. - * `:description` - Description of a field, useful for introspection. + * `:description` - Description of a field, useful for introspection. If no description + is provided, the field will inherit the description of its referenced type during + introspection (e.g., a field of type `:user` will inherit the User type's description). * `:deprecation` - Deprecation information for a field, usually set-up using `Absinthe.Schema.Notation.deprecate/1`. * `:type` - The type the value of the field should resolve to diff --git a/lib/mix/tasks/absinthe.schema.json.ex b/lib/mix/tasks/absinthe.schema.json.ex index 450e247cfe..285887e06e 100644 --- a/lib/mix/tasks/absinthe.schema.json.ex +++ b/lib/mix/tasks/absinthe.schema.json.ex @@ -98,7 +98,14 @@ defmodule Mix.Tasks.Absinthe.Schema.Json do schema: schema, json_codec: json_codec }) do - with {:ok, result} <- Absinthe.Schema.introspect(schema) do + adapter = + if function_exported?(schema, :__absinthe_adapter__, 0) do + schema.__absinthe_adapter__() + else + Absinthe.Adapter.LanguageConventions + end + + with {:ok, result} <- Absinthe.Schema.introspect(schema, adapter: adapter) do content = json_codec.encode!(result, pretty: pretty) {:ok, content} end diff --git a/lib/mix/tasks/absinthe.schema.sdl.ex b/lib/mix/tasks/absinthe.schema.sdl.ex index bb15b594a4..683b0ba572 100644 --- a/lib/mix/tasks/absinthe.schema.sdl.ex +++ b/lib/mix/tasks/absinthe.schema.sdl.ex @@ -67,12 +67,19 @@ defmodule Mix.Tasks.Absinthe.Schema.Sdl do |> Absinthe.Pipeline.upto({Absinthe.Phase.Schema.Validation.Result, pass: :final}) |> Absinthe.Schema.apply_modifiers(schema) + adapter = + if function_exported?(schema, :__absinthe_adapter__, 0) do + schema.__absinthe_adapter__() + else + Absinthe.Adapter.LanguageConventions + end + with {:ok, blueprint, _phases} <- Absinthe.Pipeline.run( schema.__absinthe_blueprint__(), pipeline ) do - {:ok, inspect(blueprint, pretty: true)} + {:ok, inspect(blueprint, pretty: true, adapter: adapter)} else _ -> {:error, "Failed to render schema"} end diff --git a/mix.exs b/mix.exs index 352cc18a08..9a9b1aaaef 100644 --- a/mix.exs +++ b/mix.exs @@ -77,6 +77,7 @@ defmodule Absinthe.Mixfile do [ {:nimble_parsec, "~> 1.2.2 or ~> 1.3"}, {:telemetry, "~> 1.0 or ~> 0.4"}, + {:credo, "~> 1.1", only: [:dev, :test], runtime: false, override: true}, {:dataloader, "~> 1.0.0 or ~> 2.0", optional: true}, {:decimal, "~> 2.0", optional: true}, {:opentelemetry_process_propagator, "~> 0.3 or ~> 0.2.1", optional: true}, diff --git a/mix.lock b/mix.lock index ee5f2a1e62..0f7d6bbd22 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{ "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "dataloader": {:hex, :dataloader, "2.0.1", "fa06b057b432b993203003fbff5ff040b7f6483a77e732b7dfc18f34ded2634f", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1 or ~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "da7ff00890e1b14f7457419b9508605a8e66ae2cc2d08c5db6a9f344550efa11"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, @@ -8,6 +10,7 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},