Skip to content

Commit ccfd7c7

Browse files
jwaldripclaude
andcommitted
feat: add TypeSystem directives support with introspection
This adds full TypeSystem directive support as requested in issue absinthe-graphql#1003: - Add `applied_directives` field to all Type structs (Object, Scalar, Field, Interface, Union, Enum, InputObject, Argument, Enum.Value) - Preserve applied directives through the build phase from Blueprint to final Type structs - Add `__AppliedDirective` and `__DirectiveArgument` introspection types - Add `appliedDirectives` field to `__Type`, `__Field`, `__InputValue`, and `__EnumValue` introspection types - TypeSystem directives can be applied to all spec-defined locations: SCHEMA, SCALAR, OBJECT, FIELD_DEFINITION, ARGUMENT_DEFINITION, INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD_DEFINITION The directive `expand` callback continues to work for transforming type definitions at compile time, and applied directives are now visible through introspection queries. Closes absinthe-graphql#1003 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a035261 commit ccfd7c7

File tree

18 files changed

+696
-1
lines changed

18 files changed

+696
-1
lines changed

lib/absinthe/blueprint/schema/enum_type_definition.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ defmodule Absinthe.Blueprint.Schema.EnumTypeDefinition do
3636
values: values_by(type_def, :identifier),
3737
values_by_internal_value: values_by(type_def, :value),
3838
values_by_name: values_by(type_def, :name),
39+
applied_directives: Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(type_def.directives),
3940
definition: type_def.module,
4041
description: type_def.description
4142
}
@@ -52,7 +53,8 @@ defmodule Absinthe.Blueprint.Schema.EnumTypeDefinition do
5253
__reference__: value_def.__reference__,
5354
__private__: value_def.__private__,
5455
description: value_def.description,
55-
deprecation: value_def.deprecation
56+
deprecation: value_def.deprecation,
57+
applied_directives: Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(value_def.directives)
5658
}
5759

5860
{Map.fetch!(value_def, key), value}

lib/absinthe/blueprint/schema/input_object_type_definition.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ defmodule Absinthe.Blueprint.Schema.InputObjectTypeDefinition do
3737
name: type_def.name,
3838
fields: build_fields(type_def, schema),
3939
description: type_def.description,
40+
applied_directives: Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(type_def.directives),
4041
definition: type_def.module
4142
}
4243
end
@@ -49,6 +50,7 @@ defmodule Absinthe.Blueprint.Schema.InputObjectTypeDefinition do
4950
description: field_def.description,
5051
name: field_def.name,
5152
type: Blueprint.TypeReference.to_type(field_def.type, schema),
53+
applied_directives: Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(field_def.directives),
5254
definition: type_def.module,
5355
__reference__: field_def.__reference__,
5456
__private__: field_def.__private__,

lib/absinthe/blueprint/schema/interface_type_definition.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ defmodule Absinthe.Blueprint.Schema.InterfaceTypeDefinition do
4444
fields: Blueprint.Schema.ObjectTypeDefinition.build_fields(type_def, schema),
4545
identifier: type_def.identifier,
4646
resolve_type: type_def.resolve_type,
47+
applied_directives: Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(type_def.directives),
4748
definition: type_def.module,
4849
interfaces: type_def.interfaces
4950
}

lib/absinthe/blueprint/schema/object_type_definition.ex

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ defmodule Absinthe.Blueprint.Schema.ObjectTypeDefinition do
4848
description: type_def.description,
4949
fields: build_fields(type_def, schema),
5050
interfaces: type_def.interfaces,
51+
applied_directives: build_applied_directives(type_def.directives),
5152
definition: type_def.module,
5253
is_type_of: type_def.is_type_of,
5354
__private__: type_def.__private__
@@ -67,6 +68,7 @@ defmodule Absinthe.Blueprint.Schema.ObjectTypeDefinition do
6768
name: field_def.name,
6869
type: Blueprint.TypeReference.to_type(field_def.type, schema),
6970
args: build_args(field_def, schema),
71+
applied_directives: build_applied_directives(field_def.directives),
7072
definition: field_def.module,
7173
__reference__: field_def.__reference__,
7274
__private__: field_def.__private__
@@ -85,6 +87,7 @@ defmodule Absinthe.Blueprint.Schema.ObjectTypeDefinition do
8587
type: Blueprint.TypeReference.to_type(arg_def.type, schema),
8688
default_value: arg_def.default_value,
8789
deprecation: arg_def.deprecation,
90+
applied_directives: build_applied_directives(arg_def.directives),
8891
__reference__: arg_def.__reference__,
8992
__private__: arg_def.__private__
9093
}
@@ -93,6 +96,43 @@ defmodule Absinthe.Blueprint.Schema.ObjectTypeDefinition do
9396
end)
9497
end
9598

99+
@doc """
100+
Converts Blueprint.Directive structs to a simple format for introspection.
101+
"""
102+
def build_applied_directives(directives) when is_list(directives) do
103+
Enum.map(directives, fn directive ->
104+
%{
105+
name: directive.name,
106+
args: Enum.map(directive.arguments, fn arg ->
107+
%{
108+
name: arg.name,
109+
value: serialize_argument_value(arg.input_value)
110+
}
111+
end)
112+
}
113+
end)
114+
end
115+
116+
def build_applied_directives(_), do: []
117+
118+
defp serialize_argument_value(%Absinthe.Blueprint.Input.String{value: value}), do: inspect(value)
119+
defp serialize_argument_value(%Absinthe.Blueprint.Input.Integer{value: value}), do: to_string(value)
120+
defp serialize_argument_value(%Absinthe.Blueprint.Input.Float{value: value}), do: to_string(value)
121+
defp serialize_argument_value(%Absinthe.Blueprint.Input.Boolean{value: value}), do: to_string(value)
122+
defp serialize_argument_value(%Absinthe.Blueprint.Input.Null{}), do: "null"
123+
defp serialize_argument_value(%Absinthe.Blueprint.Input.Enum{value: value}), do: value
124+
defp serialize_argument_value(%Absinthe.Blueprint.Input.List{items: items}) do
125+
"[" <> Enum.map_join(items, ", ", &serialize_argument_value/1) <> "]"
126+
end
127+
defp serialize_argument_value(%Absinthe.Blueprint.Input.Object{fields: fields}) do
128+
"{" <> Enum.map_join(fields, ", ", fn field ->
129+
"#{field.name}: #{serialize_argument_value(field.input_value)}"
130+
end) <> "}"
131+
end
132+
defp serialize_argument_value(%Absinthe.Blueprint.Input.RawValue{content: content}), do: serialize_argument_value(content)
133+
defp serialize_argument_value(%Absinthe.Blueprint.Input.Value{raw: raw}), do: serialize_argument_value(raw)
134+
defp serialize_argument_value(value), do: inspect(value)
135+
96136
defimpl Inspect do
97137
defdelegate inspect(term, options),
98138
to: Absinthe.Schema.Notation.SDL.Render

lib/absinthe/blueprint/schema/scalar_type_definition.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ defmodule Absinthe.Blueprint.Schema.ScalarTypeDefinition do
3636
identifier: type_def.identifier,
3737
name: type_def.name,
3838
description: type_def.description,
39+
applied_directives: Absinthe.Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(type_def.directives),
3940
definition: type_def.module,
4041
serialize: type_def.serialize,
4142
parse: type_def.parse,

lib/absinthe/blueprint/schema/union_type_definition.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ defmodule Absinthe.Blueprint.Schema.UnionTypeDefinition do
3939
identifier: type_def.identifier,
4040
types: type_def.types |> atomize_types(schema),
4141
fields: build_fields(type_def, schema),
42+
applied_directives: Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(type_def.directives),
4243
definition: type_def.module,
4344
resolve_type: type_def.resolve_type
4445
}
@@ -63,6 +64,7 @@ defmodule Absinthe.Blueprint.Schema.UnionTypeDefinition do
6364
name: field_def.name,
6465
type: Blueprint.TypeReference.to_type(field_def.type, schema),
6566
args: build_args(field_def, schema),
67+
applied_directives: Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(field_def.directives),
6668
definition: field_def.module,
6769
__reference__: field_def.__reference__,
6870
__private__: field_def.__private__
@@ -81,6 +83,7 @@ defmodule Absinthe.Blueprint.Schema.UnionTypeDefinition do
8183
type: Blueprint.TypeReference.to_type(arg_def.type, schema),
8284
default_value: arg_def.default_value,
8385
deprecation: arg_def.deprecation,
86+
applied_directives: Blueprint.Schema.ObjectTypeDefinition.build_applied_directives(arg_def.directives),
8487
__reference__: arg_def.__reference__,
8588
__private__: arg_def.__private__
8689
}

lib/absinthe/type/argument.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ defmodule Absinthe.Type.Argument do
3535
type: nil,
3636
deprecation: nil,
3737
default_value: nil,
38+
applied_directives: [],
3839
definition: nil,
3940
__reference__: nil,
4041
__private__: []

lib/absinthe/type/built_ins/introspection.ex

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,36 @@ defmodule Absinthe.Type.BuiltIns.Introspection do
9494
enum :__directive_location,
9595
values: Absinthe.Introspection.DirectiveLocation.values()
9696

97+
@desc "Represents a directive applied to a type, field, argument, or other schema element"
98+
object :__applied_directive, name: "__AppliedDirective" do
99+
field :name,
100+
type: non_null(:string),
101+
resolve: fn _, %{source: source} ->
102+
{:ok, source.name}
103+
end
104+
105+
field :args,
106+
type: non_null(list_of(non_null(:__directive_argument))),
107+
resolve: fn _, %{source: source} ->
108+
{:ok, Map.get(source, :args, [])}
109+
end
110+
end
111+
112+
@desc "Represents an argument of an applied directive"
113+
object :__directive_argument, name: "__DirectiveArgument" do
114+
field :name,
115+
type: non_null(:string),
116+
resolve: fn _, %{source: source} ->
117+
{:ok, source.name}
118+
end
119+
120+
field :value,
121+
type: non_null(:string),
122+
resolve: fn _, %{source: source} ->
123+
{:ok, source.value}
124+
end
125+
end
126+
97127
object :__type do
98128
description "Represents scalars, interfaces, object types, unions, enums in the system"
99129

@@ -214,6 +244,12 @@ defmodule Absinthe.Type.BuiltIns.Introspection do
214244
_, _ ->
215245
{:ok, nil}
216246
end
247+
248+
field :applied_directives,
249+
type: non_null(list_of(non_null(:__applied_directive))),
250+
resolve: fn _, %{source: source} ->
251+
{:ok, Map.get(source, :applied_directives, [])}
252+
end
217253
end
218254

219255
object :__field do
@@ -277,6 +313,12 @@ defmodule Absinthe.Type.BuiltIns.Introspection do
277313
_, %{source: %{deprecation: dep}} ->
278314
{:ok, dep.reason}
279315
end
316+
317+
field :applied_directives,
318+
type: non_null(list_of(non_null(:__applied_directive))),
319+
resolve: fn _, %{source: source} ->
320+
{:ok, Map.get(source, :applied_directives, [])}
321+
end
280322
end
281323

282324
object :__inputvalue, name: "__InputValue" do
@@ -327,6 +369,12 @@ defmodule Absinthe.Type.BuiltIns.Introspection do
327369
_, %{source: %{deprecation: dep}} ->
328370
{:ok, dep.reason}
329371
end
372+
373+
field :applied_directives,
374+
type: non_null(list_of(non_null(:__applied_directive))),
375+
resolve: fn _, %{source: source} ->
376+
{:ok, Map.get(source, :applied_directives, [])}
377+
end
330378
end
331379

332380
object :__enumvalue, name: "__EnumValue" do
@@ -353,6 +401,12 @@ defmodule Absinthe.Type.BuiltIns.Introspection do
353401
_, %{source: %{deprecation: dep}} ->
354402
{:ok, dep.reason}
355403
end
404+
405+
field :applied_directives,
406+
type: non_null(list_of(non_null(:__applied_directive))),
407+
resolve: fn _, %{source: source} ->
408+
{:ok, Map.get(source, :applied_directives, [])}
409+
end
356410
end
357411

358412
def render_default_value(schema, adapter, type, value) do

lib/absinthe/type/enum.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ defmodule Absinthe.Type.Enum do
8383
values: %{},
8484
values_by_internal_value: %{},
8585
values_by_name: %{},
86+
applied_directives: [],
8687
__private__: [],
8788
definition: nil,
8889
__reference__: nil

lib/absinthe/type/enum/value.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ defmodule Absinthe.Type.Enum.Value do
3838
value: nil,
3939
deprecation: nil,
4040
enum_identifier: nil,
41+
applied_directives: [],
4142
__reference__: nil,
4243
__private__: []
4344
end

0 commit comments

Comments
 (0)