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

Validations #625

Merged
merged 16 commits into from
Nov 1, 2018
5 changes: 2 additions & 3 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ locals_without_parens = [
import_fields: 1,
import_types: 1,
input_object: 3,
instruction: 1,
interface: 1,
interface: 3,
interfaces: 1,
Expand All @@ -42,13 +41,13 @@ locals_without_parens = [
types: 1,
union: 3,
value: 1,
value: 2,
value: 2
]

[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"],
locals_without_parens: locals_without_parens,
export: [
locals_without_parens: locals_without_parens,
locals_without_parens: locals_without_parens
]
]
7 changes: 7 additions & 0 deletions lib/absinthe/blueprint/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule Absinthe.Blueprint.Schema do

alias __MODULE__

alias Absinthe.Blueprint

@type type_t ::
Schema.EnumTypeDefinition.t()
| Schema.InputObjectTypeDefinition.t()
Expand Down Expand Up @@ -131,6 +133,11 @@ defmodule Absinthe.Blueprint.Schema do
build_types(rest, [concat(schema, :type_definitions, sdl_definitions) | stack], buff)
end

defp build_types([{:locations, locations} | rest], [directive | stack], buff) do
directive = Map.update!(directive, :locations, &(locations ++ &1))
build_types(rest, [directive | stack], buff)
end

defp build_types([{attr, value} | rest], [entity | stack], buff) do
entity = %{entity | attr => value}
build_types(rest, [entity | stack], buff)
Expand Down
3 changes: 2 additions & 1 deletion lib/absinthe/blueprint/schema/schema_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ defmodule Absinthe.Blueprint.Schema.SchemaDefinition do
flags: %{},
imports: [],
errors: [],
__private__: []
__private__: [],
__reference__: nil

@type t :: %__MODULE__{
description: nil | String.t(),
Expand Down
2 changes: 2 additions & 0 deletions lib/absinthe/blueprint/type_reference.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ defmodule Absinthe.Blueprint.TypeReference do
value
end

def unwrap(value) when is_atom(value), do: value

def unwrap(%struct{of_type: inner}) when struct in @wrappers do
unwrap(inner)
end
Expand Down
1 change: 1 addition & 0 deletions lib/absinthe/language/input_object_type_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ defmodule Absinthe.Language.InputObjectTypeDefinition do
defimpl Blueprint.Draft do
def convert(node, doc) do
%Blueprint.Schema.InputObjectTypeDefinition{
identifier: node.name |> Macro.underscore() |> String.to_atom(),
name: node.name,
description: node.description,
fields:
Expand Down
5 changes: 4 additions & 1 deletion lib/absinthe/language/input_value_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ defmodule Absinthe.Language.InputValueDefinition do
identifier: Macro.underscore(node.name) |> String.to_atom(),
default_value: Blueprint.Draft.convert(node.default_value, doc),
directives: Blueprint.Draft.convert(node.directives, doc),
source_location: source_location(node)
source_location: source_location(node),
__reference__: %{
location: source_location(node) |> Map.put(:file, "TODO")
}
}
end

Expand Down
2 changes: 1 addition & 1 deletion lib/absinthe/logger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule Absinthe.Logger do
end

@doc false
@spec document(Absinthe.Pipeline.data_t()) :: iolist
@spec document(Absinthe.Pipeline.data_t()) :: binary
def document(value) when value in ["", nil] do
"[EMPTY]"
end
Expand Down
19 changes: 19 additions & 0 deletions lib/absinthe/phase/schema/normalize_references.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule Absinthe.Phase.Schema.NormalizeReferences do
@moduledoc false

# TODO: this is a temporary hack to be removed before 1.5 final.

use Absinthe.Phase
alias Absinthe.Blueprint

def run(blueprint, _opts) do
blueprint = Blueprint.prewalk(blueprint, &normalize_references/1)
{:ok, blueprint}
end

def normalize_references(%Blueprint.TypeReference.Name{name: name}) do
name |> Macro.underscore() |> String.to_atom()
end

def normalize_references(node), do: node
end
17 changes: 10 additions & 7 deletions lib/absinthe/phase/schema/type_imports.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
defmodule Absinthe.Phase.Schema.TypeImports do
@moduledoc false

def run(blueprint, _opts) do
blueprint =
Map.update!(blueprint, :schema_definitions, fn schemas ->
for schema <- schemas, do: handle_imports(schema)
end)
use Absinthe.Phase
alias Absinthe.Blueprint

alias Absinthe.Blueprint.Schema

def run(blueprint, _opts) do
blueprint = Blueprint.prewalk(blueprint, &handle_imports/1)
{:ok, blueprint}
end

Expand All @@ -15,14 +16,16 @@ defmodule Absinthe.Phase.Schema.TypeImports do
{Absinthe.Type.BuiltIns.Directives, []},
{Absinthe.Type.BuiltIns.Introspection, []}
]
def handle_imports(schema) do
def handle_imports(%Schema.SchemaDefinition{} = schema) do
types = do_imports(@default_imports ++ schema.imports, schema.type_definitions)
# special casing the import of the built in directives
[builtins] = Absinthe.Type.BuiltIns.Directives.__absinthe_blueprint__().schema_definitions
directives = schema.directive_definitions ++ builtins.directive_definitions
%{schema | type_definitions: types, directive_definitions: directives}
{:halt, %{schema | type_definitions: types, directive_definitions: directives}}
end

def handle_imports(node), do: node

defp do_imports([], types) do
types
end
Expand Down
60 changes: 0 additions & 60 deletions lib/absinthe/phase/schema/validate_type_references.ex

This file was deleted.

78 changes: 78 additions & 0 deletions lib/absinthe/phase/schema/validation/default_enum_value_present.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
defmodule Absinthe.Phase.Schema.Validation.DefaultEnumValuePresent do
use Absinthe.Phase
alias Absinthe.Blueprint
alias Absinthe.Blueprint.Schema

def run(blueprint, _opts) do
blueprint = Blueprint.prewalk(blueprint, &validate_schema/1)

{:ok, blueprint}
end

def validate_schema(%Schema.SchemaDefinition{} = schema) do
enums =
schema.type_definitions
|> Enum.filter(&match?(%Schema.EnumTypeDefinition{}, &1))
|> Map.new(&{&1.identifier, &1})

schema = Blueprint.prewalk(schema, &validate_defaults(&1, enums))
{:halt, schema}
end

def validate_schema(node), do: node

def validate_defaults(%{default_value: nil} = node, _) do
node
end

def validate_defaults(%{default_value: default, type: type} = node, enums) do
type = Blueprint.TypeReference.unwrap(type)

case Map.fetch(enums, type) do
{:ok, enum} ->
values = Enum.map(enum.values, & &1.value)
value_list = Enum.map(values, &"\n * #{inspect(&1)}")

if not (default in values) do
detail = %{
value_list: value_list,
type: type,
default_value: default
}

node |> put_error(error(node, detail))
else
node
end

_ ->
node
end
end

def validate_defaults(node, _) do
node
end

defp error(node, data) do
%Absinthe.Phase.Error{
message: explanation(data),
locations: [node.__reference__.location],
phase: __MODULE__,
extra: data
}
end

@moduledoc false

def explanation(%{default_value: default_value, type: type, value_list: value_list}) do
"""
The default_value for an enum must be present in the enum values.

Could not use default value of "#{default_value}" for #{inspect(type)}.

Valid values are:
#{value_list}
"""
end
end
Loading