Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow schema declaration directives #1176

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Bug Fix: [Add `__private__` field to EnumValueDefinition](https://github.com/absinthe-graphql/absinthe/pull/1148)
- Bug Fix: [Fix bug in Schema.**absinthe_types**(:all) for Persistent Term](https://github.com/absinthe-graphql/absinthe/pull/1161)
- Feature: [Add `import_directives` macro](https://github.com/absinthe-graphql/absinthe/pull/1158)
- Feature: [Support type extensions on schema declarations](https://github.com/absinthe-graphql/absinthe/pull/1176)

## 1.7.0

Expand Down
23 changes: 22 additions & 1 deletion lib/absinthe/blueprint/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ defmodule Absinthe.Blueprint.Schema do
Schema.InterfaceTypeDefinition,
Schema.UnionTypeDefinition,
Schema.EnumValueDefinition,
Schema.TypeExtensionDefinition
Schema.TypeExtensionDefinition,
Schema.SchemaDeclaration
]

defp build_types([%module{} = type | rest], stack, buff) when module in @simple_open do
Expand Down Expand Up @@ -196,6 +197,7 @@ defmodule Absinthe.Blueprint.Schema do
Schema.InterfaceTypeDefinition,
Schema.ObjectTypeDefinition,
Schema.ScalarTypeDefinition,
Schema.SchemaDeclaration,
Schema.UnionTypeDefinition
]
defp build_types(
Expand All @@ -210,6 +212,14 @@ defmodule Absinthe.Blueprint.Schema do
build_types(rest, [%{extend | definition: def} | stack], buff)
end

defp build_types(
[:close | rest],
[%Schema.FieldDefinition{} = field, %Schema.SchemaDeclaration{} = declaration | stack],
buff
) do
build_types(rest, [push(declaration, :field_definitions, field) | stack], buff)
end

defp build_types([:close | rest], [%Schema.FieldDefinition{} = field, obj | stack], buff) do
field =
field
Expand Down Expand Up @@ -271,6 +281,17 @@ defmodule Absinthe.Blueprint.Schema do
build_types(rest, [schema | stack], buff)
end

defp build_types(
[:close | rest],
[%Schema.SchemaDeclaration{} = schema_declaration, schema | stack],
buff
) do
# The declaration is pushed into the :type_definitions instead of the :schema_declaration
# as it will be split off later in the ApplyDeclaration phase
schema = push(schema, :type_definitions, schema_declaration)
build_types(rest, [schema | stack], buff)
end

defp build_types([:close | rest], [%Schema.SchemaDefinition{} = schema, bp], buff) do
bp = push(bp, :schema_definitions, schema)
build_types(rest, [bp], buff)
Expand Down
4 changes: 3 additions & 1 deletion lib/absinthe/blueprint/transform.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,13 @@ defmodule Absinthe.Blueprint.Transform do
Blueprint.Schema.InterfaceTypeDefinition => [:interfaces, :fields, :directives],
Blueprint.Schema.ObjectTypeDefinition => [:interfaces, :fields, :directives],
Blueprint.Schema.ScalarTypeDefinition => [:directives],
Blueprint.Schema.SchemaDeclaration => [:directives, :field_definitions],
Blueprint.Schema.SchemaDefinition => [
:directive_definitions,
:type_definitions,
:type_extensions,
:directives
:directives,
:schema_declaration
],
Blueprint.Schema.TypeExtensionDefinition => [:definition],
Blueprint.Schema.UnionTypeDefinition => [:directives, :types]
Expand Down
34 changes: 33 additions & 1 deletion lib/absinthe/phase/schema/apply_declaration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,18 @@ defmodule Absinthe.Phase.Schema.ApplyDeclaration do
}

[] ->
schema_definition
declaration = build_declaration(schema_definition.type_definitions)

root_mappings =
declaration
|> extract_root_mappings

%{
schema_definition
| type_definitions:
Enum.map(schema_definition.type_definitions, &maybe_mark_root(&1, root_mappings)),
schema_declaration: declaration
}

[_first | extra_declarations] ->
extra_declarations
Expand Down Expand Up @@ -113,4 +124,25 @@ defmodule Absinthe.Phase.Schema.ApplyDeclaration do
false
end)
end

defp build_declaration(type_definitions) do
field_definitions =
type_definitions
|> Enum.filter(&(&1.identifier in ~w(query mutation subscription)a))
|> Enum.map(fn type_def ->
%Blueprint.Schema.FieldDefinition{
module: __MODULE__,
name: to_string(type_def.identifier),
identifier: type_def.identifier,
type: %Blueprint.TypeReference.Name{name: type_def.name},
__reference__: Absinthe.Schema.Notation.build_reference(__ENV__)
}
end)

%Blueprint.Schema.SchemaDeclaration{
module: __MODULE__,
__reference__: Absinthe.Schema.Notation.build_reference(__ENV__),
field_definitions: field_definitions
}
end
end
27 changes: 25 additions & 2 deletions lib/absinthe/phase/schema/apply_type_extensions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ defmodule Absinthe.Phase.Schema.ApplyTypeExtensions do
end

def update_schema_defs(schema_definitions) do
for schema_def = %{type_definitions: type_definitions, type_extensions: type_extensions} <-
for schema_def = %{
type_definitions: type_definitions,
type_extensions: type_extensions,
schema_declaration: schema_declaration
} <-
schema_definitions do
{type_definitions, type_extensions} =
apply_type_extensions(type_definitions, type_extensions, [])

{[schema_declaration], type_extensions} =
apply_type_extensions([schema_declaration], type_extensions, [])

%{
schema_def
| type_definitions: type_definitions,
| schema_declaration: schema_declaration,
type_definitions: type_definitions,
type_extensions: type_extensions
}
end
Expand Down Expand Up @@ -125,6 +133,21 @@ defmodule Absinthe.Phase.Schema.ApplyTypeExtensions do
}}
end

defp apply_extension(
%Schema.TypeExtensionDefinition{
definition: %Schema.SchemaDeclaration{}
} = extension,
%Schema.SchemaDeclaration{} = definition
) do
{extension,
%{
definition
| field_definitions:
definition.field_definitions ++ extension.definition.field_definitions,
directives: definition.directives ++ extension.definition.directives
}}
end

defp apply_extension(
%{definition: %{identifier: identifier} = extension},
%{identifier: identifier} = definition
Expand Down
4 changes: 4 additions & 0 deletions lib/absinthe/phase/schema/compile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ defmodule Absinthe.Phase.Schema.Compile do
unquote(Macro.escape(prototype_schema))
end

def __absinthe_schema_declaration__() do
unquote(Macro.escape(schema.schema_declaration))
end

unquote_splicing(metadata)
end

Expand Down
3 changes: 2 additions & 1 deletion lib/absinthe/phase/schema/populate_persistent_term.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ if Code.ensure_loaded?(:persistent_term) do
__absinthe_prototype_schema__: prototype_schema,
__absinthe_type__: types_map,
__absinthe_directive__: directives_map,
__absinthe_reference__: metadata
__absinthe_reference__: metadata,
__absinthe_schema_declaration__: schema.schema_declaration
}

schema_name = opts[:schema] || raise "no schema name provided"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ defmodule Absinthe.Phase.Schema.Validation.TypeReferencesExist do
end

def validate_types(%Blueprint.Schema.TypeExtensionDefinition{} = extension, types) do
check_or_error(extension, extension.definition.identifier, types)
case extension.definition do
%Blueprint.Schema.SchemaDeclaration{} = declaration ->
declaration

definition ->
check_or_error(extension, definition.identifier, types)
end
end

@no_types [
Expand All @@ -61,6 +67,7 @@ defmodule Absinthe.Phase.Schema.Validation.TypeReferencesExist do
Blueprint.Schema.ObjectTypeDefinition,
Blueprint.Schema.ScalarTypeDefinition,
Blueprint.Schema.SchemaDefinition,
Blueprint.Schema.SchemaDeclaration,
Blueprint.TypeReference.NonNull,
Blueprint.TypeReference.ListOf,
Absinthe.Blueprint.TypeReference.Name
Expand Down
2 changes: 1 addition & 1 deletion lib/absinthe/pipeline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ defmodule Absinthe.Pipeline do
{Phase.Schema.DirectiveImports, options},
{Phase.Schema.TypeImports, options},
{Phase.Schema.TypeExtensionImports, options},
Phase.Schema.ApplyTypeExtensions,
Phase.Schema.ApplyDeclaration,
Phase.Schema.ApplyTypeExtensions,
Phase.Schema.Introspection,
{Phase.Schema.Hydrate, options},
Phase.Schema.Arguments.Normalize,
Expand Down
8 changes: 8 additions & 0 deletions lib/absinthe/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ defmodule Absinthe.Schema do
@schema_provider.__absinthe_interface_implementors__(__MODULE__)
end

def __absinthe_schema_declaration__() do
@schema_provider.__absinthe_schema_declaration__(__MODULE__)
end

def __absinthe_prototype_schema__() do
@prototype_schema
end
Expand Down Expand Up @@ -547,6 +551,10 @@ defmodule Absinthe.Schema do
end
end

def schema_declaration(schema) do
schema.__absinthe_schema_declaration__()
end

@doc """
Get all concrete types for union, interface, or object
"""
Expand Down
4 changes: 4 additions & 0 deletions lib/absinthe/schema/compiled.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ defmodule Absinthe.Schema.Compiled do
def __absinthe_interface_implementors__(schema_mod) do
Module.concat([schema_mod, Compiled]).__absinthe_interface_implementors__
end

def __absinthe_schema_declaration__(schema_mod) do
Module.concat([schema_mod, Compiled]).__absinthe_schema_declaration__
end
end
67 changes: 59 additions & 8 deletions lib/absinthe/schema/notation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,8 @@ defmodule Absinthe.Schema.Notation do
end
```
"""
@reserved_identifiers ~w(query mutation subscription)a
defmacro object(identifier, attrs \\ [], block)

defmacro object(identifier, _attrs, _block) when identifier in @reserved_identifiers do
raise Absinthe.Schema.Notation.Error,
"Invalid schema notation: cannot create an `object` " <>
"with reserved identifier `#{identifier}`"
end

defmacro object(identifier, attrs, do: block) do
block = block_from_directive_attrs(attrs, block)

Expand Down Expand Up @@ -300,6 +293,41 @@ defmodule Absinthe.Schema.Notation do
|> record_extend!([], block, [])
end

defmacro extend({:schema, meta, _}, do: block) do
block = {:schema, meta, [] ++ [[do: block]]}

__CALLER__
|> recordable!(:extend, @placement[:extend])
|> record_extend!([], block, [])
end

@placement {:schema, [toplevel: true, extend: true]}
@doc """
Declare a schema

Optional declaration of the schema. Useful if you want to add directives
to your schema declaration

## Placement

#{Utils.placement_docs(@placement)}

## Examples

```
schema do
directive :feature
field :query, :query
# ...
end
```
"""
defmacro schema(do: block) do
__CALLER__
|> recordable!(:schema, @placement[:schema])
|> record_schema!(block)
end

@placement {:deprecate, [under: [:field]]}
@doc """
Mark a field as deprecated
Expand Down Expand Up @@ -477,7 +505,7 @@ defmodule Absinthe.Schema.Notation do
end

# FIELDS
@placement {:field, [under: [:input_object, :interface, :object]]}
@placement {:field, [under: [:input_object, :interface, :object, :schema_declaration]]}
@doc """
Defines a GraphQL field

Expand Down Expand Up @@ -932,6 +960,7 @@ defmodule Absinthe.Schema.Notation do
:interface,
:object,
:scalar,
:schema_declaration,
:union,
:value
]
Expand Down Expand Up @@ -1592,6 +1621,25 @@ defmodule Absinthe.Schema.Notation do
]
end

def record_schema!(env, block) do
attrs =
[]
|> Keyword.put(:module, env.module)
|> put_reference(env)

definition = struct!(Schema.SchemaDeclaration, attrs)

ref = put_attr(env.module, definition)

push_stack(env.module, :absinthe_scope_stack, :schema_declaration)

[
get_desc(ref),
block,
quote(do: unquote(__MODULE__).close_scope())
]
end

defp handle_extend_attrs(attrs, caller) do
block =
case Keyword.get(attrs, :meta) do
Expand Down Expand Up @@ -2382,6 +2430,9 @@ defmodule Absinthe.Schema.Notation do
defp recordable?([toplevel: true, extend: true], scope),
do: scope == :schema || scope == :extend

defp recordable?([toplevel: false, extend: true], scope),
do: scope == :extend

defp recordable?([toplevel: true], scope), do: scope == :schema
defp recordable?([toplevel: false], scope), do: scope != :schema

Expand Down
Loading