From fe82fa42cba37d8cad192f552133a0cb5017a753 Mon Sep 17 00:00:00 2001
From: spawnia <benedikt@franke.tech>
Date: Wed, 16 May 2018 10:17:32 +0200
Subject: [PATCH 1/6] Improvements to validation and field resolvers

- Limit the depth of static rule generation to prevent infinite loops with circularly nested Input Objects
- Move validation from trait to the generic field class. This makes it so that Queries and Mutations are treated the same in regards to validation.
- Unify the generated classes for Fields, Mutations and Queries
- Require Fields to implement type() via abstract method
- Include default resolver in Field class to be able to apply authentication, authorization and validation even if no resolver is implemented
- Deprecate the rules() function in favour of inline arguments defined within args()
- Add tests and documentation for all proposed changes
---
 Readme.md                                     | 147 +++++----
 src/Folklore/GraphQL/Console/stubs/field.stub |  37 ++-
 .../GraphQL/Console/stubs/mutation.stub       |  26 +-
 src/Folklore/GraphQL/Console/stubs/query.stub |  26 +-
 src/Folklore/GraphQL/GraphQL.php              |  24 +-
 src/Folklore/GraphQL/Support/Field.php        | 299 +++++++++++++++---
 src/Folklore/GraphQL/Support/Mutation.php     |  13 +-
 src/Folklore/GraphQL/Support/Query.php        |   9 +-
 .../GraphQL/Support/Traits/ShouldValidate.php | 133 --------
 src/config/config.php                         |  10 +-
 tests/ConfigTest.php                          |  25 +-
 tests/FieldTest.php                           |  28 +-
 tests/GraphQLQueryTest.php                    |  29 +-
 tests/GraphQLTest.php                         |  18 +-
 tests/MutationTest.php                        | 169 +++++-----
 ...Object.php => ExampleChildInputObject.php} |  17 +-
 tests/Objects/ExampleField.php                |   7 +-
 tests/Objects/ExampleMutation.php             |  91 ++++++
 tests/Objects/ExampleParentInputObject.php    |  52 +++
 tests/Objects/ExampleValidationField.php      |  12 +-
 .../Objects/ExampleValidationInputObject.php  |  45 ---
 tests/Objects/ExamplesContextQuery.php        |   4 +-
 tests/Objects/ExamplesPaginationQuery.php     |   5 +-
 tests/Objects/ExamplesQuery.php               |  13 +-
 tests/Objects/ExamplesRootQuery.php           |   4 +-
 tests/Objects/UpdateExampleMutation.php       |  56 ----
 .../UpdateExampleMutationWithInputType.php    |  71 -----
 tests/Objects/queries.php                     |  10 +-
 tests/TestCase.php                            |   8 +-
 29 files changed, 743 insertions(+), 645 deletions(-)
 delete mode 100644 src/Folklore/GraphQL/Support/Traits/ShouldValidate.php
 rename tests/Objects/{ExampleNestedValidationInputObject.php => ExampleChildInputObject.php} (54%)
 create mode 100644 tests/Objects/ExampleMutation.php
 create mode 100644 tests/Objects/ExampleParentInputObject.php
 delete mode 100644 tests/Objects/ExampleValidationInputObject.php
 delete mode 100644 tests/Objects/UpdateExampleMutation.php
 delete mode 100644 tests/Objects/UpdateExampleMutationWithInputType.php

diff --git a/Readme.md b/Readme.md
index d660d35a..0694edcc 100644
--- a/Readme.md
+++ b/Readme.md
@@ -127,7 +127,7 @@ config/graphql.php
 - [Schemas](#schemas)
 - [Creating a query](#creating-a-query)
 - [Creating a mutation](#creating-a-mutation)
-- [Adding validation to mutation](#adding-validation-to-mutation)
+- [Input Validation](#validation)
 
 #### Advanced Usage
 - [Query variables](docs/advanced.md#query-variables)
@@ -418,91 +418,100 @@ if you use homestead:
 http://homestead.app/graphql?query=mutation+users{updateUserPassword(id: "1", password: "newpassword"){id,email}}
 ```
 
-#### Adding validation to mutation
+### Validation
 
-It is possible to add validation rules to mutation. It uses the laravel `Validator` to performs validation against the `args`.
+It is possible to add additional validation rules to inputs, using the Laravel `Validator` to perform validation against the `args`.
+Validation is mostly used for Mutations, but can also be applied to Queries that take arguments.
 
-When creating a mutation, you can add a method to define the validation rules that apply by doing the following:
+Be aware that GraphQL has native types to define a field as either a List or as NonNull. Use those wrapping
+types instead of native Laravel validation via `array` or `required`. This way, those constraints are
+reflected through the schema and are validated by the underlying GraphQL implementation.
 
-```php
-namespace App\GraphQL\Mutation;
+#### Rule definition
 
-use GraphQL;
-use GraphQL\Type\Definition\Type;
-use Folklore\GraphQL\Support\Mutation;
-use App\User;
+You can add validation rules directly to the arguments of Mutations or Queries:
+
+
+```php
+//...
 
 class UpdateUserEmailMutation extends Mutation
 {
-    protected $attributes = [
-        'name' => 'UpdateUserEmail'
-    ];
-
-    public function type()
-    {
-        return GraphQL::type('User');
-    }
+    //...
 
     public function args()
-    {
-        return [
-            'id' => ['name' => 'id', 'type' => Type::string()],
-            'email' => ['name' => 'email', 'type' => Type::string()]
-        ];
-    }
-
-    public function rules()
-    {
-        return [
-            'id' => ['required'],
-            'email' => ['required', 'email']
-        ];
-    }
-
-    public function resolve($root, $args)
-    {
-        $user = User::find($args['id']);
-
-        if (!$user) {
-            return null;
+        {
+            return [
+                'id' => [
+                    'name' => 'id',
+                    'type' => Type::nonNull(Type::string()),
+                    // Adding a rule for 'required' is not necessary
+                ],
+                'email' => [
+                    'name' => 'email',
+                    'type' => Type::nonNull(Type::string()),
+                    'rules' => ['email']
+                ],
+                'links' => [
+                    'name' => 'links',
+                    'type' => Type::listOf(Type::string()),
+                    // Validation is applied to each element of the list 
+                    'rules' => ['url']
+                ],
+                'name' => [
+                    'name' => 'name',
+                    'type' => GraphQL::type('NameInputObject')
+                ],
+            ];
         }
 
-        $user->email = $args['email'];
-        $user->save();
-
-        return $user;
-    }
+    //...
 }
 ```
 
-Alternatively you can define rules with each args
+#### Input Object Rules
 
-```php
-class UpdateUserEmailMutation extends Mutation
+Notice how the argument `name` has the type `NameInputObject`? The rules defined in Input Objects are also considered when
+validating the Mutation! The definition of those rules looks like this:
+
+````php
+<?php
+
+use GraphQL\Type\Definition\Type;
+use Folklore\GraphQL\Support\Type as BaseType;
+
+class NameInputObject extends BaseType
 {
-    //...
+    protected $inputObject = true;
 
-    public function args()
+    protected $attributes = [
+        'name' => 'NameInputObject'
+    ];
+    
+    public function fields()
     {
         return [
-            'id' => [
-                'name' => 'id',
+            'first' => [
+                'name' => 'first',
                 'type' => Type::string(),
-                'rules' => ['required']
+                'rules' => ['alpha']
+            ],
+            'last' => [
+                'name' => 'last',
+                'type' => Type::nonNull(Type::string()),
+                'rules' => ['alpha']
             ],
-            'email' => [
-                'name' => 'email',
-                'type' => Type::string(),
-                'rules' => ['required', 'email']
-            ]
         ];
     }
-
-    //...
 }
-```
+```` 
 
-When you execute a mutation, it will returns the validation errors. Since GraphQL specifications define a certain format for errors, the validation errors messages are added to the error object as a extra `validation` attribute. To find the validation error, you should check for the error with a `message` equals to `'validation'`, then the `validation` attribute will contain the normal errors messages returned by the Laravel Validator.
+Now, the rules in here ensure that if a name is passed to the above Mutation, it must contain at least a
+last name, and the first and last name can only contain alphabetic characters.
+
+#### Response format
+
+When you execute a mutation, it will return the validation errors. Since the GraphQL specification defines a certain format for errors, the validation error messages are added to the error object as an extra `validation` attribute. To find the validation error, you should check for the error with a `message` equals to `'validation'`, then the `validation` attribute will contain the normal errors messages returned by the Laravel Validator.
 
 ```json
 {
@@ -527,3 +536,19 @@ When you execute a mutation, it will returns the validation errors. Since GraphQ
   ]
 }
 ```
+
+#### Validation depth
+
+Laravel validation works by statically generating validation rules and then applying
+them to arguments all at once. However, field arguments may consist of Input Objects, which
+could have circular references. Because of this, the maximum depth for validation has to be
+set before. The default depth is set to `10`, it can be overwritten this way:
+
+````php
+class ExampleMutation extends Mutation
+{
+    protected $validationDepth = 15;
+    
+    //...
+}
+````
diff --git a/src/Folklore/GraphQL/Console/stubs/field.stub b/src/Folklore/GraphQL/Console/stubs/field.stub
index e7003d00..22c96f9f 100644
--- a/src/Folklore/GraphQL/Console/stubs/field.stub
+++ b/src/Folklore/GraphQL/Console/stubs/field.stub
@@ -4,23 +4,52 @@ namespace DummyNamespace;
 
 use GraphQL\Type\Definition\Type;
 use Folklore\GraphQL\Support\Field;
+use GraphQL\Type\Definition\ResolveInfo;
 
-class DummyClass extends Field {
+class DummyClass extends Field
+{
     protected $attributes = [
+        'name' => 'DummyField',
         'description' => 'A field'
     ];
 
+    /**
+     * Define a GraphQL Type which is returned by this field.
+     *
+     * @return \Folklore\GraphQL\Support\Type
+     */
     public function type()
     {
-        return Type::string();
+        return Type::listOf(Type::string());
     }
 
+    /**
+     * Define the arguments expected by the field.
+     *
+     * @return array
+     */
     public function args()
     {
-        return [];
+        return [
+            'example' => [
+                'name' => 'example',
+                'type' => Type::nonNull(Type::string()),
+                'rules' => ['alpha', 'not_in:forbidden,value']
+            ]
+        ];
     }
 
-    protected function resolve($root, $args)
+    /**
+     * Return a result for the field which should match up with its return type.
+     *
+     * @param $root
+     * @param $args
+     * @param $context
+     * @param ResolveInfo $info
+     * @return array
+     */
+    public function resolve($root, $args, $context, ResolveInfo $info)
     {
+        return [];
     }
 }
diff --git a/src/Folklore/GraphQL/Console/stubs/mutation.stub b/src/Folklore/GraphQL/Console/stubs/mutation.stub
index 837319bf..80619327 100644
--- a/src/Folklore/GraphQL/Console/stubs/mutation.stub
+++ b/src/Folklore/GraphQL/Console/stubs/mutation.stub
@@ -5,7 +5,6 @@ namespace DummyNamespace;
 use Folklore\GraphQL\Support\Mutation;
 use GraphQL\Type\Definition\ResolveInfo;
 use GraphQL\Type\Definition\Type;
-use GraphQL;
 
 class DummyClass extends Mutation
 {
@@ -14,18 +13,41 @@ class DummyClass extends Mutation
         'description' => 'A mutation'
     ];
 
+    /**
+     * Define a GraphQL Type which is returned by this field.
+     *
+     * @return \Folklore\GraphQL\Support\Type
+     */
     public function type()
     {
         return Type::listOf(Type::string());
     }
 
+    /**
+     * Define the arguments expected by the field.
+     *
+     * @return array
+     */
     public function args()
     {
         return [
-            
+            'example' => [
+                'name' => 'example',
+                'type' => Type::nonNull(Type::string()),
+                'rules' => ['alpha', 'not_in:forbidden,value']
+            ]
         ];
     }
 
+    /**
+     * Return a result for the field which should match up with its return type.
+     *
+     * @param $root
+     * @param $args
+     * @param $context
+     * @param ResolveInfo $info
+     * @return array
+     */
     public function resolve($root, $args, $context, ResolveInfo $info)
     {
         return [];
diff --git a/src/Folklore/GraphQL/Console/stubs/query.stub b/src/Folklore/GraphQL/Console/stubs/query.stub
index 6ec158bc..0092d20d 100644
--- a/src/Folklore/GraphQL/Console/stubs/query.stub
+++ b/src/Folklore/GraphQL/Console/stubs/query.stub
@@ -5,7 +5,6 @@ namespace DummyNamespace;
 use Folklore\GraphQL\Support\Query;
 use GraphQL\Type\Definition\ResolveInfo;
 use GraphQL\Type\Definition\Type;
-use GraphQL;
 
 class DummyClass extends Query
 {
@@ -14,18 +13,41 @@ class DummyClass extends Query
         'description' => 'A query'
     ];
 
+    /**
+     * Define a GraphQL Type which is returned by this field.
+     *
+     * @return \Folklore\GraphQL\Support\Type
+     */
     public function type()
     {
         return Type::listOf(Type::string());
     }
 
+    /**
+     * Define the arguments expected by the field.
+     *
+     * @return array
+     */
     public function args()
     {
         return [
-            
+            'example' => [
+                'name' => 'example',
+                'type' => Type::nonNull(Type::string()),
+                'rules' => ['alpha', 'not_in:forbidden,value']
+            ]
         ];
     }
 
+    /**
+     * Return a result for the field which should match up with its return type.
+     *
+     * @param $root
+     * @param $args
+     * @param $context
+     * @param ResolveInfo $info
+     * @return array
+     */
     public function resolve($root, $args, $context, ResolveInfo $info)
     {
         return [];
diff --git a/src/Folklore/GraphQL/GraphQL.php b/src/Folklore/GraphQL/GraphQL.php
index 53cfb98c..230fbfdc 100644
--- a/src/Folklore/GraphQL/GraphQL.php
+++ b/src/Folklore/GraphQL/GraphQL.php
@@ -1,23 +1,17 @@
 <?php namespace Folklore\GraphQL;
 
-use Folklore\GraphQL\Support\Contracts\TypeConvertible;
-use GraphQL\GraphQL as GraphQLBase;
-use GraphQL\Type\Schema;
-use GraphQL\Error\Error;
-
-use GraphQL\Type\Definition\ObjectType;
-
 use Folklore\GraphQL\Error\ValidationError;
-use Folklore\GraphQL\Error\AuthorizationError;
-
-use Folklore\GraphQL\Exception\TypeNotFound;
-use Folklore\GraphQL\Exception\SchemaNotFound;
-
 use Folklore\GraphQL\Events\SchemaAdded;
 use Folklore\GraphQL\Events\TypeAdded;
-
-use Folklore\GraphQL\Support\PaginationType;
+use Folklore\GraphQL\Exception\SchemaNotFound;
+use Folklore\GraphQL\Exception\TypeNotFound;
+use Folklore\GraphQL\Support\Contracts\TypeConvertible;
 use Folklore\GraphQL\Support\PaginationCursorType;
+use Folklore\GraphQL\Support\PaginationType;
+use GraphQL\Error\Error;
+use GraphQL\GraphQL as GraphQLBase;
+use GraphQL\Type\Definition\ObjectType;
+use GraphQL\Type\Schema;
 
 class GraphQL
 {
@@ -162,7 +156,7 @@ public function queryAndReturnResult($query, $variables = [], $opts = [])
         $context = array_get($opts, 'context', null);
         $schemaName = array_get($opts, 'schema', null);
         $operationName = array_get($opts, 'operationName', null);
-        $defaultFieldResolver = config('graphql.defaultFieldResolver', null);
+        $defaultFieldResolver = config('graphql.defaultFieldResolver', [\GraphQL\Executor\Executor::class, 'defaultFieldResolver']);
 
         $additionalResolversSchemaName = is_string($schemaName) ? $schemaName : config('graphql.schema', 'default');
         $additionalResolvers = config('graphql.resolvers.' . $additionalResolversSchemaName, []);
diff --git a/src/Folklore/GraphQL/Support/Field.php b/src/Folklore/GraphQL/Support/Field.php
index b730b0e3..79f90fc2 100644
--- a/src/Folklore/GraphQL/Support/Field.php
+++ b/src/Folklore/GraphQL/Support/Field.php
@@ -2,130 +2,325 @@
 
 namespace Folklore\GraphQL\Support;
 
-use Illuminate\Support\Fluent;
 use Folklore\GraphQL\Error\AuthorizationError;
+use Folklore\GraphQL\Error\ValidationError;
+use GraphQL\Type\Definition\InputObjectField;
+use GraphQL\Type\Definition\InputObjectType;
+use GraphQL\Type\Definition\ListOfType;
+use GraphQL\Type\Definition\ResolveInfo;
+use GraphQL\Type\Definition\WrappingType;
+use Illuminate\Contracts\Validation\Validator;
+use Illuminate\Support\Fluent;
 
-class Field extends Fluent
+/**
+ * This is a generic class for fields.
+ *
+ * It should only be used directly for custom fields, if you want to have
+ * a Query or Mutation, extend those classes instead.
+ *
+ * Class Field
+ * @package Folklore\GraphQL\Support
+ */
+abstract class Field extends Fluent
 {
+    /**
+     * Validation rules for the Field arguments.
+     *
+     * @var array
+     */
+    protected $rules = [];
 
     /**
-     * Override this in your queries or mutations
-     * to provide custom authorization
+     * How many levels of nesting should validation be applied to.
+     *
+     * @var int
+     */
+    protected $validationDepth = 10;
+
+    /**
+     * Override this in your queries or mutations to provide custom authorization.
+     *
+     * @param $root
+     * @param $args
+     *
+     * @return bool
      */
     public function authorize($root, $args)
     {
+        // Default to allowing authorization
         return true;
     }
 
     /**
-     * Override this in your queries or mutations
-     * to authenticate per query or mutation
+     * Override this in your queries or mutations to authenticate per query or mutation.
+     *
+     * @param $root
+     * @param $args
+     * @param $context
+     *
+     * @return bool
      */
     public function authenticated($root, $args, $context)
     {
+        // Default to allow authentication
         return true;
     }
 
-    public function attributes()
+    /**
+     * Convert the Fluent instance to an array.
+     *
+     * @return array
+     */
+    public function toArray()
     {
-        return [];
+        return $this->getAttributes();
     }
 
-    public function type()
+    /**
+     * Get the attributes from the container.
+     *
+     * @return array
+     */
+    public function getAttributes()
     {
-        return null;
+        return array_merge(
+            $this->attributes,
+            [
+                'args' => $this->args(),
+                'type' => $this->type(),
+                'resolve' => $this->getResolver(),
+            ]
+        );
     }
 
+    /**
+     * Define the arguments expected by the field.
+     *
+     * @return array
+     */
     public function args()
     {
         return [];
     }
 
-    protected function getResolver()
+    /**
+     * Define a GraphQL Type which is returned by this field.
+     *
+     * @return \Folklore\GraphQL\Support\Type
+     */
+    public abstract function type();
+
+    /**
+     * Return a result for the field.
+     *
+     * @param $root
+     * @param $args
+     * @param $context
+     * @param ResolveInfo $info
+     * @return mixed
+     */
+    public function resolve($root, $args, $context, ResolveInfo $info)
     {
-        if (!method_exists($this, 'resolve')) {
-            return null;
-        }
+        // Use the default resolver, can be overridden in custom Fields, Queries or Mutations
+        // This enables us to still apply authentication, authorization and validation upon the query
+        return call_user_func(config('graphql.defaultFieldResolver'));
+    }
 
-        $resolver = array($this, 'resolve');
-        $authenticate = [$this, 'authenticated'];
+    /**
+     * Returns a function that wraps the resolve function with authentication and validation checks.
+     *
+     * @return \Closure
+     */
+    protected function getResolver()
+    {
+        $authenticated = [$this, 'authenticated'];
         $authorize = [$this, 'authorize'];
+        $resolve = [$this, 'resolve'];
 
-        return function () use ($resolver, $authorize, $authenticate) {
+        return function () use ($authenticated, $authorize, $resolve) {
             $args = func_get_args();
 
-            // Authenticated
-            if (call_user_func_array($authenticate, $args) !== true) {
+            // Check authentication first
+            if (call_user_func_array($authenticated, $args) !== true) {
                 throw new AuthorizationError('Unauthenticated');
             }
 
-            // Authorize
+            // After authentication, check specific authorization
             if (call_user_func_array($authorize, $args) !== true) {
                 throw new AuthorizationError('Unauthorized');
             }
 
-            return call_user_func_array($resolver, $args);
+            // Apply additional validation
+            $rules = call_user_func_array([$this, 'getRules'], $args);
+            if (sizeof($rules)) {
+                $validationErrorMessages = call_user_func_array([$this, 'validationErrorMessages'], $args);
+                $inputArguments = array_get($args, 1, []);
+                $validator = $this->getValidator($inputArguments, $rules, $validationErrorMessages);
+                if ($validator->fails()) {
+                    throw with(new ValidationError('validation'))->setValidator($validator);
+                }
+            }
+
+            return call_user_func_array($resolve, $args);
         };
     }
 
     /**
-     * Get the attributes from the container.
+     * @param $args
+     * @param $rules
+     * @param array $messages
+     *
+     * @return Validator
+     */
+    protected function getValidator($args, $rules, $messages = [])
+    {
+        /** @var Validator $validator */
+        $validator = app('validator')->make($args, $rules, $messages);
+        if (method_exists($this, 'withValidator')) {
+            $this->withValidator($validator, $args);
+        }
+
+        return $validator;
+    }
+
+    /**
+     * Dynamically retrieve the value of an attribute.
+     *
+     * @param  string $key
+     * @return mixed
+     */
+    public function __get($key)
+    {
+        $attributes = $this->getAttributes();
+        return isset($attributes[$key]) ? $attributes[$key] : null;
+    }
+
+    /**
+     * Dynamically check if an attribute is set.
+     *
+     * @param  string $key
+     * @return bool
+     */
+    public function __isset($key)
+    {
+        $attributes = $this->getAttributes();
+        return isset($attributes[$key]);
+    }
+
+    /**
+     * Get the combined explicit and type-inferred rules.
      *
      * @return array
      */
-    public function getAttributes()
+    public function getRules()
     {
-        $attributes = $this->attributes();
-        $args = $this->args();
+        $arguments = func_get_args();
+
+        $argsRules = $this->getRulesFromArgs($this->args(), $this->validationDepth, null, $arguments);
 
-        $attributes = array_merge($this->attributes, [
-            'args' => $args
-        ], $attributes);
+        // Merge rules that were set separately with those defined through args
+        $explicitRules = call_user_func_array([$this, 'rules'], $arguments);
+        return array_merge($argsRules, $explicitRules);
+    }
 
-        $type = $this->type();
-        if (isset($type)) {
-            $attributes['type'] = $type;
+    /**
+     * @param $fields
+     * @param $inputValueDepth
+     * @param $parentKey
+     * @param $resolutionArguments
+     * @return array
+     */
+    protected function getRulesFromArgs($fields, $inputValueDepth, $parentKey, $resolutionArguments)
+    {
+        // At depth 0, there are still field rules to gather
+        // once we get below 0, we have gathered all the rules necessary
+        // for the nesting depth
+        if ($inputValueDepth < 0) {
+            return [];
         }
 
-        $resolver = $this->getResolver();
-        if (isset($resolver)) {
-            $attributes['resolve'] = $resolver;
+        // We are going one level deeper
+        $inputValueDepth--;
+
+        $rules = [];
+        // Merge in the rules of the Input Type
+        foreach ($fields as $fieldName => $field) {
+            // Count the depth per field
+            $fieldDepth = $inputValueDepth;
+
+            // If given, add the parent key
+            $key = $parentKey ? "{$parentKey}.{$fieldName}" : $fieldName;
+
+            // The values passed in here may be of different types, depending on where they were defined
+            if ($field instanceof InputObjectField) {
+                // We can depend on type being set, since a field without type is not valid
+                $type = $field->type;
+                // Rules are optional so they may not be set
+                $fieldRules = isset($field->rules) ? $field->rules : [];
+            } else {
+                $type = $field['type'];
+                $fieldRules = isset($field['rules']) ? $field['rules'] : [];
+            }
+
+            // Unpack until we get to the root type
+            while ($type instanceof WrappingType) {
+                if ($type instanceof ListOfType) {
+                    // This lets us skip one level of validation rules
+                    $fieldDepth--;
+                    // Add this to the prefix to allow Laravel validation for arrays
+                    $key .= '.*';
+                }
+
+                $type = $type->getWrappedType();
+            }
+
+            // Add explicitly set rules if they apply
+            if (sizeof($fieldRules)) {
+                $rules[$key] = $this->resolveRules($fieldRules, $resolutionArguments);
+            }
+
+            if ($type instanceof InputObjectType) {
+                // Recursively call the parent method to get nested rules, passing in the new prefix
+                $rules = array_merge($rules, $this->getRulesFromArgs($type->getFields(), $fieldDepth, $key, $resolutionArguments));
+            }
         }
 
-        return $attributes;
+        return $rules;
     }
 
     /**
-     * Convert the Fluent instance to an array.
-     *
-     * @return array
+     * @param $rules
+     * @param $arguments
+     * @return mixed
      */
-    public function toArray()
+    protected function resolveRules($rules, $arguments)
     {
-        return $this->getAttributes();
+        // Rules can be defined as closures
+        if (is_callable($rules)) {
+            return call_user_func_array($rules, $arguments);
+        }
+
+        return $rules;
     }
 
     /**
-     * Dynamically retrieve the value of an attribute.
+     * Can be overwritten to define rules.
      *
-     * @param  string  $key
-     * @return mixed
+     * @deprecated Will be removed in favour of defining rules together with the args.
+     * @return array
      */
-    public function __get($key)
+    protected function rules()
     {
-        $attributes = $this->getAttributes();
-        return isset($attributes[$key]) ? $attributes[$key]:null;
+        return [];
     }
 
     /**
-     * Dynamically check if an attribute is set.
+     * Return an array of custom validation error messages.
      *
-     * @param  string  $key
-     * @return void
+     * @return array
      */
-    public function __isset($key)
+    protected function validationErrorMessages($root, $args, $context)
     {
-        $attributes = $this->getAttributes();
-        return isset($attributes[$key]);
+        return [];
     }
 }
diff --git a/src/Folklore/GraphQL/Support/Mutation.php b/src/Folklore/GraphQL/Support/Mutation.php
index e4b710be..bed633f7 100644
--- a/src/Folklore/GraphQL/Support/Mutation.php
+++ b/src/Folklore/GraphQL/Support/Mutation.php
@@ -2,11 +2,12 @@
 
 namespace Folklore\GraphQL\Support;
 
-use Validator;
-use Folklore\GraphQL\Error\ValidationError;
-use Folklore\GraphQL\Support\Traits\ShouldValidate;
-
-class Mutation extends Field
+/**
+ * Mutations are just fields which are expected to change state on the server.
+ *
+ * Class Mutation
+ * @package Folklore\GraphQL\Support
+ */
+abstract class Mutation extends Field
 {
-    use ShouldValidate;
 }
diff --git a/src/Folklore/GraphQL/Support/Query.php b/src/Folklore/GraphQL/Support/Query.php
index c59e09ae..12c70f44 100644
--- a/src/Folklore/GraphQL/Support/Query.php
+++ b/src/Folklore/GraphQL/Support/Query.php
@@ -2,7 +2,12 @@
 
 namespace Folklore\GraphQL\Support;
 
-class Query extends Field
+/**
+ * Queries are simply Fields which are supposed to be idempotent.
+ *
+ * Class Query
+ * @package Folklore\GraphQL\Support
+ */
+abstract class Query extends Field
 {
-    
 }
diff --git a/src/Folklore/GraphQL/Support/Traits/ShouldValidate.php b/src/Folklore/GraphQL/Support/Traits/ShouldValidate.php
deleted file mode 100644
index 03ecf5f2..00000000
--- a/src/Folklore/GraphQL/Support/Traits/ShouldValidate.php
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-
-namespace Folklore\GraphQL\Support\Traits;
-
-use Folklore\GraphQL\Error\ValidationError;
-use GraphQL\Type\Definition\InputObjectType;
-use GraphQL\Type\Definition\ListOfType;
-use GraphQL\Type\Definition\WrappingType;
-
-trait ShouldValidate
-{
-    protected function rules()
-    {
-        return [];
-    }
-
-    /**
-     * Return an array of custom validation error messages.
-     *
-     * @return array
-     */
-    public function validationErrorMessages($root, $args, $context)
-    {
-        return [];
-    }
-
-    public function getRules()
-    {
-        $arguments = func_get_args();
-
-        $rules = call_user_func_array([$this, 'rules'], $arguments);
-        $argsRules = [];
-        foreach ($this->args() as $name => $arg) {
-            if (isset($arg['rules'])) {
-                $argsRules[$name] = $this->resolveRules($arg['rules'], $arguments);
-            }
-
-            if (isset($arg['type'])) {
-                $argsRules = array_merge($argsRules, $this->inferRulesFromType($arg['type'], $name, $arguments));
-            }
-        }
-
-        return array_merge($rules, $argsRules);
-    }
-
-    public function resolveRules($rules, $arguments)
-    {
-        if (is_callable($rules)) {
-            return call_user_func_array($rules, $arguments);
-        }
-
-        return $rules;
-    }
-
-    public function inferRulesFromType($type, $prefix, $resolutionArguments)
-    {
-        $rules = [];
-
-        // if it is an array type, add an array validation component
-        if ($type instanceof ListOfType) {
-            $prefix = "{$prefix}.*";
-        }
-
-        // make sure we are dealing with the actual type
-        if ($type instanceof WrappingType) {
-            $type = $type->getWrappedType();
-        }
-
-        // if it is an input object type - the only type we care about here...
-        if ($type instanceof InputObjectType) {
-            // merge in the input type's rules
-            $rules = array_merge($rules, $this->getInputTypeRules($type, $prefix, $resolutionArguments));
-        }
-
-        // Ignore scalar types
-
-        return $rules;
-    }
-
-    public function getInputTypeRules(InputObjectType $input, $prefix, $resolutionArguments)
-    {
-        $rules = [];
-
-        foreach ($input->getFields() as $name => $field) {
-            $key = "{$prefix}.{$name}";
-
-            // get any explicitly set rules
-            if (isset($field->rules)) {
-                $rules[$key] = $this->resolveRules($field->rules, $resolutionArguments);
-            }
-
-            // then recursively call the parent method to see if this is an
-            // input object, passing in the new prefix
-            $rules = array_merge($rules, $this->inferRulesFromType($field->type, $key, $resolutionArguments));
-        }
-
-        return $rules;
-    }
-
-    protected function getValidator($args, $rules, $messages = [])
-    {
-        $validator =  app('validator')->make($args, $rules, $messages);
-        if (method_exists($this, 'withValidator')) {
-            $this->withValidator($validator, $args);
-        }
-
-        return $validator;
-    }
-
-    protected function getResolver()
-    {
-        $resolver = parent::getResolver();
-        if (!$resolver) {
-            return null;
-        }
-
-        return function () use ($resolver) {
-            $arguments = func_get_args();
-
-            $rules = call_user_func_array([$this, 'getRules'], $arguments);
-            $validationErrorMessages = call_user_func_array([$this, 'validationErrorMessages'], $arguments);
-            if (sizeof($rules)) {
-                $args = array_get($arguments, 1, []);
-                $validator = $this->getValidator($args, $rules, $validationErrorMessages);
-                if ($validator->fails()) {
-                    throw with(new ValidationError('validation'))->setValidator($validator);
-                }
-            }
-
-            return call_user_func_array($resolver, $arguments);
-        };
-    }
-}
diff --git a/src/config/config.php b/src/config/config.php
index 0d51e6e6..c385e7ed 100644
--- a/src/config/config.php
+++ b/src/config/config.php
@@ -152,17 +152,17 @@
     ],
 
     /*
-     * Overrides the default field resolver
-     * Useful to setup default loading of eager relationships
+     * Overrides the default field resolver.
+     *
+     * This may be useful to setup default loading of eager relationships
      *
      * Example:
      *
      * 'defaultFieldResolver' => function ($root, $args, $context, $info) {
-     *     // take a look at the defaultFieldResolver in
-     *     // https://github.com/webonyx/graphql-php/blob/master/src/Executor/Executor.php
+     *     // Implement your custom functionality here
      * },
      */
-    'defaultFieldResolver' => null,
+    'defaultFieldResolver' => [\GraphQL\Executor\Executor::class, 'defaultFieldResolver'],
 
     /*
      * The types available in the application. You can access them from the
diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php
index b57c6d23..46c4d320 100644
--- a/tests/ConfigTest.php
+++ b/tests/ConfigTest.php
@@ -1,8 +1,6 @@
 <?php
 
-use GraphQL\Type\Schema;
 use GraphQL\Utils\BuildSchema;
-use GraphQL\Type\Definition\ObjectType;
 use GraphQL\Validator\DocumentValidator;
 
 class ConfigTest extends TestCase
@@ -30,7 +28,7 @@ protected function getEnvironmentSetUp($app)
                         'examplesRoot' => ExamplesRootQuery::class
                     ],
                     'mutation' => [
-                        'updateExample' => UpdateExampleMutation::class
+                        'exampleMutation' => ExampleMutation::class
                     ]
                 ],
                 'custom' => [
@@ -38,7 +36,7 @@ protected function getEnvironmentSetUp($app)
                         'examplesCustom' => ExamplesQuery::class
                     ],
                     'mutation' => [
-                        'updateExampleCustom' => UpdateExampleMutation::class
+                        'exampleMutationCustom' => ExampleMutation::class
                     ]
                 ],
                 'shorthand' => BuildSchema::build('
@@ -62,7 +60,9 @@ protected function getEnvironmentSetUp($app)
 
             'types' => [
                 'Example' => ExampleType::class,
-                CustomExampleType::class
+                CustomExampleType::class,
+                'ExampleParentInputObject' => ExampleParentInputObject::class,
+                'ExampleChildInputObject' => ExampleChildInputObject::class,
             ],
 
             'security' => [
@@ -79,7 +79,7 @@ public function testRouteQuery()
             'query' => $this->queries['examplesCustom']
         ]);
 
-        $this->assertEquals($response->getStatusCode(), 200);
+        $this->assertEquals(200, $response->getStatusCode());
 
         $content = $response->getData(true);
         $this->assertArrayHasKey('data', $content);
@@ -88,10 +88,13 @@ public function testRouteQuery()
     public function testRouteMutation()
     {
         $response = $this->call('POST', '/graphql_test/mutation', [
-            'query' => $this->queries['updateExampleCustom']
+            'query' => $this->queries['exampleMutation'],
+            'params' => [
+                'required' => 'test'
+            ],
         ]);
 
-        $this->assertEquals($response->getStatusCode(), 200);
+        $this->assertEquals(200, $response->getStatusCode());
 
         $content = $response->getData(true);
         $this->assertArrayHasKey('data', $content);
@@ -130,7 +133,7 @@ public function testVariablesInputName()
             ]
         ]);
 
-        $this->assertEquals($response->getStatusCode(), 200);
+        $this->assertEquals(200, $response->getStatusCode());
 
         $content = $response->getData(true);
         $this->assertArrayHasKey('data', $content);
@@ -150,7 +153,7 @@ public function testVariablesInputNameForShorthandResolver()
             ],
         ]);
 
-        $this->assertEquals($response->getStatusCode(), 200);
+        $this->assertEquals(200, $response->getStatusCode());
 
         $content = $response->getData(true);
         $this->assertArrayHasKey('data', $content);
@@ -181,6 +184,6 @@ public function testErrorFormatter()
             'graphql.error_formatter' => [$error, 'formatError']
         ]);
 
-        $result = GraphQL::query($this->queries['examplesWithError']);
+        GraphQL::query($this->queries['examplesWithError']);
     }
 }
diff --git a/tests/FieldTest.php b/tests/FieldTest.php
index 53817610..90654841 100644
--- a/tests/FieldTest.php
+++ b/tests/FieldTest.php
@@ -1,16 +1,26 @@
 <?php
 
-use Folklore\Support\Field;
-use GraphQL\Type\Definition\Type;
-use GraphQL\Type\Definition\ObjectType;
+use Folklore\GraphQL\Support\Field;
 
 class FieldTest extends TestCase
 {
+    /**
+     * @return string
+     */
     protected function getFieldClass()
     {
         return ExampleField::class;
     }
 
+    /**
+     * @return Field
+     */
+    protected function getFieldInstance()
+    {
+        $class = $this->getFieldClass();
+        return new $class();
+    }
+
     /**
      * Test get attributes
      *
@@ -18,8 +28,7 @@ protected function getFieldClass()
      */
     public function testGetAttributes()
     {
-        $class = $this->getFieldClass();
-        $field = new $class();
+        $field = $this->getFieldInstance();
         $attributes = $field->getAttributes();
         
         $this->assertArrayHasKey('name', $attributes);
@@ -32,11 +41,11 @@ public function testGetAttributes()
     }
     
     /**
-     * Test resolve closure
+     * Test the calling of a custom resolve function.
      *
      * @test
      */
-    public function testResolve()
+    public function testResolveFunctionIsCalled()
     {
         $class = $this->getFieldClass();
         $field = $this->getMockBuilder($class)
@@ -47,7 +56,7 @@ public function testResolve()
             ->method('resolve');
         
         $attributes = $field->getAttributes();
-        $attributes['resolve'](null, [], [], null);
+        $attributes['resolve'](null, [], [], new \GraphQL\Type\Definition\ResolveInfo([]));
     }
        
     /**
@@ -57,8 +66,7 @@ public function testResolve()
      */
     public function testToArray()
     {
-        $class = $this->getFieldClass();
-        $field = new $class();
+        $field = $this->getFieldInstance();
         $array = $field->toArray();
         
         $this->assertInternalType('array', $array);
diff --git a/tests/GraphQLQueryTest.php b/tests/GraphQLQueryTest.php
index f8a71a99..d0d2cc7b 100644
--- a/tests/GraphQLQueryTest.php
+++ b/tests/GraphQLQueryTest.php
@@ -1,11 +1,5 @@
 <?php
 
-use GraphQL\Type\Schema;
-use GraphQL\Type\Definition\ObjectType;
-use GraphQL\Type\Definition\Type;
-use GraphQL\Error\Error;
-use Folklore\GraphQL\Error\ValidationError;
-
 class GraphQLQueryTest extends TestCase
 {
     /**
@@ -166,32 +160,35 @@ public function testQueryWithError()
     }
 
     /**
-     * Test query with validation error
+     * Test query with validation that passes successfully.
      *
      * @test
      */
-    public function testQueryWithValidationError()
+    public function testQueryWithValidationSuccess()
     {
-        $result = GraphQL::query($this->queries['examplesWithValidation']);
+        $result = GraphQL::query($this->queries['examplesWithValidation'], [
+            'index' => 0
+        ]);
 
         $this->assertArrayHasKey('data', $result);
-        $this->assertArrayHasKey('errors', $result);
-        $this->assertArrayHasKey('validation', $result['errors'][0]);
-        $this->assertTrue($result['errors'][0]['validation']->has('index'));
+        $this->assertArrayNotHasKey('errors', $result);
     }
 
     /**
-     * Test query with validation without error
+     * Test query with validation error.
      *
      * @test
      */
-    public function testQueryWithValidation()
+    public function testQueryWithValidationError()
     {
         $result = GraphQL::query($this->queries['examplesWithValidation'], [
-            'index' => 0
+            // The validation requires this to be below 100
+            'index' => 9001
         ]);
 
         $this->assertArrayHasKey('data', $result);
-        $this->assertArrayNotHasKey('errors', $result);
+        $this->assertArrayHasKey('errors', $result);
+        $this->assertArrayHasKey('validation', $result['errors'][0]);
+        $this->assertTrue($result['errors'][0]['validation']->has('index'));
     }
 }
diff --git a/tests/GraphQLTest.php b/tests/GraphQLTest.php
index 143d6580..910d250d 100644
--- a/tests/GraphQLTest.php
+++ b/tests/GraphQLTest.php
@@ -1,12 +1,12 @@
 <?php
 
-use GraphQL\Type\Schema;
-use GraphQL\Type\Definition\ObjectType;
-use GraphQL\Type\Definition\Type;
-use GraphQL\Error\Error;
 use Folklore\GraphQL\Error\ValidationError;
-use Folklore\GraphQL\Events\TypeAdded;
 use Folklore\GraphQL\Events\SchemaAdded;
+use Folklore\GraphQL\Events\TypeAdded;
+use GraphQL\Error\Error;
+use GraphQL\Type\Definition\ObjectType;
+use GraphQL\Type\Definition\Type;
+use GraphQL\Type\Schema;
 
 class GraphQLTest extends TestCase
 {
@@ -21,7 +21,7 @@ public function testSchema()
         
         $this->assertGraphQLSchema($schema);
         $this->assertGraphQLSchemaHasQuery($schema, 'examples');
-        $this->assertGraphQLSchemaHasMutation($schema, 'updateExample');
+        $this->assertGraphQLSchemaHasMutation($schema, 'exampleMutation');
         $this->assertArrayHasKey('Example', $schema->getTypeMap());
     }
     
@@ -58,7 +58,7 @@ public function testSchemaWithName()
         
         $this->assertGraphQLSchema($schema);
         $this->assertGraphQLSchemaHasQuery($schema, 'examplesCustom');
-        $this->assertGraphQLSchemaHasMutation($schema, 'updateExampleCustom');
+        $this->assertGraphQLSchemaHasMutation($schema, 'exampleMutationCustom');
         $this->assertArrayHasKey('Example', $schema->getTypeMap());
     }
     
@@ -74,7 +74,7 @@ public function testSchemaWithArray()
                 'examplesCustom' => ExamplesQuery::class
             ],
             'mutation' => [
-                'updateExampleCustom' => UpdateExampleMutation::class
+                'updateExampleCustom' => ExampleMutation::class
             ],
             'types' => [
                 CustomExampleType::class
@@ -280,7 +280,7 @@ public function testAddSchema()
                 'examplesCustom' => ExamplesQuery::class
             ],
             'mutation' => [
-                'updateExampleCustom' => UpdateExampleMutation::class
+                'updateExampleCustom' => ExampleMutation::class
             ],
             'types' => [
                 CustomExampleType::class
diff --git a/tests/MutationTest.php b/tests/MutationTest.php
index 4dc4d2d5..112b03a5 100644
--- a/tests/MutationTest.php
+++ b/tests/MutationTest.php
@@ -6,142 +6,117 @@ class MutationTest extends FieldTest
 {
     protected function getFieldClass()
     {
-        return UpdateExampleMutationWithInputType::class;
-    }
-
-    protected function getEnvironmentSetUp($app)
-    {
-        parent::getEnvironmentSetUp($app);
-
-        $app['config']->set('graphql.types', [
-            'Example' => ExampleType::class,
-            'ExampleValidationInputObject' => ExampleValidationInputObject::class,
-            'ExampleNestedValidationInputObject' => ExampleNestedValidationInputObject::class,
-        ]);
+        return ExampleMutation::class;
     }
 
     /**
-     * Test get rules.
+     * Laravel based validation rules should be correctly constructed.
      *
      * @test
      */
     public function testGetRules()
     {
-        $class = $this->getFieldClass();
-        $field = new $class();
+        $field = $this->getFieldInstance();
         $rules = $field->getRules();
 
         $this->assertInternalType('array', $rules);
-        $this->assertArrayHasKey('test', $rules);
-        $this->assertArrayHasKey('test_with_rules', $rules);
-        $this->assertArrayHasKey('test_with_rules_closure', $rules);
-        $this->assertEquals($rules['test'], ['required']);
-        $this->assertEquals($rules['test_with_rules'], ['required']);
-        $this->assertEquals($rules['test_with_rules_closure'], ['required']);
-        $this->assertEquals($rules['test_with_rules_input_object'], ['required']);
-        $this->assertEquals(array_get($rules, 'test_with_rules_input_object.val'), ['required']);
-        $this->assertEquals(array_get($rules, 'test_with_rules_input_object.nest'), ['required']);
-        $this->assertEquals(array_get($rules, 'test_with_rules_input_object.nest.email'), ['email']);
-        $this->assertEquals(array_get($rules, 'test_with_rules_input_object.list'), ['required']);
-        $this->assertEquals(array_get($rules, 'test_with_rules_input_object.list.*.email'), ['email']);
-    }
 
-    /**
-     * Test resolve.
-     *
-     * @test
-     */
-    public function testResolve()
-    {
-        $class = $this->getFieldClass();
-        $field = $this->getMockBuilder($class)
-                    ->setMethods(['resolve'])
-                    ->getMock();
-
-        $field->expects($this->once())
-            ->method('resolve');
-
-        $attributes = $field->getAttributes();
-        $attributes['resolve'](null, [
-            'test' => 'test',
-            'test_with_rules' => 'test',
-            'test_with_rules_closure' => 'test',
-            'test_with_rules_input_object' => [
-                'val' => 'test',
-                'nest' => ['email' => 'test@test.com'],
-                'list' => [
-                    ['email' => 'test@test.com'],
-                ],
-            ],
-        ], [], null);
+        // Those three definitions must have the same result
+        $this->assertEquals(['email'], $rules['email_seperate_rules']);
+        $this->assertEquals(['email'], $rules['email_inline_rules']);
+        $this->assertEquals(['email'], $rules['email_closure_rules']);
+
+        // Apply array validation to values defined as GraphQL-Lists
+        $this->assertEquals(['email'], $rules['email_list.*']);
+        $this->assertEquals(['email'], $rules['email_list_of_lists.*.*']);
+
+        // Inferred rules from Input Objects
+        $this->assertEquals(['alpha'], $rules['input_object.alpha']);
+        $this->assertEquals(['email'], $rules['input_object.child.email']);
+        $this->assertEquals(['email'], $rules['input_object.child-list.*.email']);
+
+        // Self-referencing, nested InputObject
+        $this->assertEquals(['alpha'], $rules['input_object.self.alpha']);
+        $this->assertEquals(['email'], $rules['input_object.self.child.email']);
+        $this->assertEquals(['email'], $rules['input_object.self.child-list.*.email']);
+
+        // Go down a few levels
+        $this->assertEquals(['alpha'], $rules['input_object.self.self.self.self.alpha']);
+        $this->assertEquals(['email'], $rules['input_object.self.self.self.self.child.email']);
+        $this->assertEquals(['email'], $rules['input_object.self.self.self.self.child-list.*.email']);
+
+        $this->assertArrayNotHasKey(
+            'input_object.self.self.self.self.self.self.self.self.self.self.alpha',
+            $rules,
+            'Validation rules should not be set for such deep nesting.');
     }
 
     /**
      * Test resolve throw validation error.
      *
      * @test
-     * @expectedException \Folklore\GraphQL\Error\ValidationError
      */
     public function testResolveThrowValidationError()
     {
-        $class = $this->getFieldClass();
-        $field = new $class();
-
+        $field = $this->getFieldInstance();
         $attributes = $field->getAttributes();
-        $attributes['resolve'](null, [], [], null);
+
+        $this->expectException('\Folklore\GraphQL\Error\ValidationError');
+        $attributes['resolve'](null, [
+            'email_inline_rules' => 'not-an-email'
+        ], [], null);
     }
 
     /**
-     * Test validation error.
+     * Validation error messages are correctly constructed and thrown.
      *
      * @test
      */
-    public function testValidationError()
+    public function testValidationErrorMessages()
     {
-        $class = $this->getFieldClass();
-        $field = new $class();
-
+        $field = $this->getFieldInstance();
         $attributes = $field->getAttributes();
 
         try {
-            $attributes['resolve'](null, [], [], null);
+            $attributes['resolve'](null, [
+                'email_inline_rules' => 'not-an-email',
+                'email_list' => ['not-an-email', 'valid@email.com'],
+                'email_list_of_lists' => [
+                    ['valid@email.com'],
+                    ['not-an-email'],
+                ],
+                'input_object' => [
+                    'child' => [
+                        'email' => 'not-an-email'
+                    ],
+                    'self' => [
+                        'self' => [
+                            'alpha' => 'Not alphanumeric !"§)'
+                        ]
+                    ]
+                ]
+            ], [], null);
         } catch (\Folklore\GraphQL\Error\ValidationError $e) {
             $validator = $e->getValidator();
 
             $this->assertInstanceOf(Validator::class, $validator);
 
+            /** @var \Illuminate\Support\MessageBag $messages */
             $messages = $e->getValidatorMessages();
-            $this->assertTrue($messages->has('test'));
-            $this->assertTrue($messages->has('test_with_rules'));
-            $this->assertTrue($messages->has('test_with_rules_closure'));
-            $this->assertTrue($messages->has('test_with_rules_input_object.val'));
-            $this->assertTrue($messages->has('test_with_rules_input_object.nest'));
-            $this->assertTrue($messages->has('test_with_rules_input_object.list'));
-        }
-    }
-
-    /**
-     * Test custom validation error messages.
-     *
-     * @test
-     */
-    public function testCustomValidationErrorMessages()
-    {
-        $class = $this->getFieldClass();
-        $field = new $class();
-        $rules = $field->getRules();
-        $attributes = $field->getAttributes();
-        try {
-            $attributes['resolve'](null, [
-                 'test_with_rules_input_object' => [
-                     'nest' => ['email' => 'invalidTestEmail.com'],
-                 ],
-             ], [], null);
-        } catch (\Folklore\GraphQL\Error\ValidationError $e) {
-            $messages = $e->getValidatorMessages();
+            $messageKeys = $messages->keys();
+            // Ensure that validation errors occurred only where necessary, ignoring the order
+            $this->assertEquals([
+                'email_inline_rules',
+                'email_list.0',
+                'email_list_of_lists.1.0',
+                'input_object.child.email',
+                'input_object.self.self.alpha',
+            ], $messageKeys, 'Not all the right fields were validated.', 0, 10, true);
+
+            // The custom validation error message should override the default
+            $this->assertEquals('Has to be a valid email.', $messages->first('email_inline_rules'));
+            $this->assertEquals('Invalid email: not-an-email', $messages->first('input_object.child.email'));
 
-            $this->assertEquals($messages->first('test'), 'A test is required.');
-            $this->assertEquals($messages->first('test_with_rules_input_object.nest.email'), 'Invalid your email : invalidTestEmail.com');
         }
     }
 }
diff --git a/tests/Objects/ExampleNestedValidationInputObject.php b/tests/Objects/ExampleChildInputObject.php
similarity index 54%
rename from tests/Objects/ExampleNestedValidationInputObject.php
rename to tests/Objects/ExampleChildInputObject.php
index d4a394c9..fa478a64 100644
--- a/tests/Objects/ExampleNestedValidationInputObject.php
+++ b/tests/Objects/ExampleChildInputObject.php
@@ -1,22 +1,16 @@
 <?php
 
-use GraphQL\Type\Definition\Type;
-use Folklore\GraphQL\Support\Traits\ShouldValidate;
 use Folklore\GraphQL\Support\Type as BaseType;
+use GraphQL\Type\Definition\Type;
 
-class ExampleNestedValidationInputObject extends BaseType
+class ExampleChildInputObject extends BaseType
 {
     protected $inputObject = true;
 
     protected $attributes = [
-        'name' => 'ExampleNestedValidationInputObject'
+        'name' => 'ExampleChildInputObject'
     ];
 
-    public function type()
-    {
-        return Type::listOf(Type::string());
-    }
-
     public function fields()
     {
         return [
@@ -27,9 +21,4 @@ public function fields()
             ],
         ];
     }
-
-    public function resolve($root, $args)
-    {
-        return ['test'];
-    }
 }
diff --git a/tests/Objects/ExampleField.php b/tests/Objects/ExampleField.php
index 4c1face2..5e0f1c87 100644
--- a/tests/Objects/ExampleField.php
+++ b/tests/Objects/ExampleField.php
@@ -1,7 +1,7 @@
 <?php
 
-use GraphQL\Type\Definition\Type;
 use Folklore\GraphQL\Support\Field;
+use GraphQL\Type\Definition\Type;
 
 class ExampleField extends Field
 {
@@ -23,9 +23,4 @@ public function args()
             ]
         ];
     }
-
-    public function resolve($root, $args)
-    {
-        return ['test'];
-    }
 }
diff --git a/tests/Objects/ExampleMutation.php b/tests/Objects/ExampleMutation.php
new file mode 100644
index 00000000..1b6c28fc
--- /dev/null
+++ b/tests/Objects/ExampleMutation.php
@@ -0,0 +1,91 @@
+<?php
+
+use Folklore\GraphQL\Support\Facades\GraphQL;
+use Folklore\GraphQL\Support\Mutation;
+use GraphQL\Type\Definition\ResolveInfo;
+use GraphQL\Type\Definition\Type;
+
+class ExampleMutation extends Mutation
+{
+    protected $attributes = [
+        'name' => 'exampleMutation'
+    ];
+
+    public function type()
+    {
+        return GraphQL::type('Example');
+    }
+
+    public function args()
+    {
+        return [
+            'required' => [
+                'name' => 'required',
+                // Define required args through GraphQL types instead of Laravel validation
+                // This way graphql-php takes care of validating that and the requirements
+                // show up in the schema.
+                'type' => Type::nonNull(Type::string()),
+            ],
+
+            'email_seperate_rules' => [
+                'name' => 'email_seperate_rules',
+                'type' => Type::string()
+            ],
+
+            'email_inline_rules' => [
+                'name' => 'email_inline_rules',
+                'type' => Type::string(),
+                'rules' => ['email']
+            ],
+
+            'email_closure_rules' => [
+                'name' => 'email_closure_rules',
+                'type' => Type::string(),
+                'rules' => function () {
+                    return ['email'];
+                }
+            ],
+
+            'email_list' => [
+                'name' => 'email_list',
+                'type' => Type::listOf(Type::string()),
+                'rules' => ['email'],
+            ],
+
+            'email_list_of_lists' => [
+                'name' => 'email_list_of_lists',
+                'type' => Type::listOf(Type::listOf(Type::string())),
+                'rules' => ['email'],
+            ],
+
+            'input_object' => [
+                'name' => 'input_object',
+                'type' => GraphQL::type('ExampleParentInputObject'),
+            ],
+        ];
+    }
+
+    public function resolve($root, $args, $context, ResolveInfo $info)
+    {
+        return [
+            'test' => array_get($args, 'test')
+        ];
+    }
+
+    protected function rules()
+    {
+        return [
+            'email_seperate_rules' => ['email']
+        ];
+    }
+
+    protected function validationErrorMessages($root, $args, $context)
+    {
+        $invalidEmail = array_get($args, 'input_object.child.email');
+
+        return [
+            'email_inline_rules.email' => 'Has to be a valid email.',
+            'input_object.child.email.email' => 'Invalid email: ' . $invalidEmail,
+        ];
+    }
+}
diff --git a/tests/Objects/ExampleParentInputObject.php b/tests/Objects/ExampleParentInputObject.php
new file mode 100644
index 00000000..89531b1f
--- /dev/null
+++ b/tests/Objects/ExampleParentInputObject.php
@@ -0,0 +1,52 @@
+<?php
+
+use Folklore\GraphQL\Support\Type as BaseType;
+use GraphQL\Type\Definition\Type;
+
+class ExampleParentInputObject extends BaseType
+{
+    protected $inputObject = true;
+
+    protected $attributes = [
+        'name' => 'ExampleParentInputObject',
+    ];
+
+    public function type()
+    {
+        return Type::listOf(Type::string());
+    }
+
+    public function fields()
+    {
+        return [
+
+            'alpha' => [
+                'name' => 'alpha',
+                'type' => Type::nonNull(Type::string()),
+                'rules' => ['alpha'],
+            ],
+
+            'child' => [
+                'name' => 'child',
+                'type' => GraphQL::type('ExampleChildInputObject'),
+            ],
+
+            'child-list' => [
+                'name' => 'child-list',
+                'type' => Type::listOf(GraphQL::type('ExampleChildInputObject')),
+            ],
+
+            // Reference itself. Used in test for avoiding infinite loop when creating validation rules
+            'self' => [
+                'name' => 'self',
+                'type' => GraphQL::type('ExampleParentInputObject'),
+            ],
+
+        ];
+    }
+
+    public function resolve($root, $args)
+    {
+        return ['test'];
+    }
+}
diff --git a/tests/Objects/ExampleValidationField.php b/tests/Objects/ExampleValidationField.php
index c2edece5..3de80a65 100644
--- a/tests/Objects/ExampleValidationField.php
+++ b/tests/Objects/ExampleValidationField.php
@@ -1,13 +1,10 @@
 <?php
 
-use GraphQL\Type\Definition\Type;
 use Folklore\GraphQL\Support\Field;
-use Folklore\GraphQL\Support\Traits\ShouldValidate;
+use GraphQL\Type\Definition\Type;
 
 class ExampleValidationField extends Field
 {
-    use ShouldValidate;
-    
     protected $attributes = [
         'name' => 'example_validation'
     ];
@@ -23,13 +20,8 @@ public function args()
             'index' => [
                 'name' => 'index',
                 'type' => Type::int(),
-                'rules' => ['required']
+                'rules' => ['integer', 'max:100']
             ]
         ];
     }
-
-    public function resolve($root, $args)
-    {
-        return ['test'];
-    }
 }
diff --git a/tests/Objects/ExampleValidationInputObject.php b/tests/Objects/ExampleValidationInputObject.php
deleted file mode 100644
index 738eae58..00000000
--- a/tests/Objects/ExampleValidationInputObject.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-use GraphQL\Type\Definition\Type;
-use Folklore\GraphQL\Support\Traits\ShouldValidate;
-use Folklore\GraphQL\Support\Type as BaseType;
-
-class ExampleValidationInputObject extends BaseType
-{
-    protected $inputObject = true;
-
-    protected $attributes = [
-        'name' => 'ExampleValidationInputObject'
-    ];
-
-    public function type()
-    {
-        return Type::listOf(Type::string());
-    }
-
-    public function fields()
-    {
-        return [
-            'val' => [
-                'name' => 'val',
-                'type' => Type::int(),
-                'rules' => ['required']
-            ],
-            'nest' => [
-                'name' => 'nest',
-                'type' => GraphQL::type('ExampleNestedValidationInputObject'),
-                'rules' => ['required']
-            ],
-            'list' => [
-                'name' => 'list',
-                'type' => Type::listOf(GraphQL::type('ExampleNestedValidationInputObject')),
-                'rules' => ['required']
-            ],
-        ];
-    }
-
-    public function resolve($root, $args)
-    {
-        return ['test'];
-    }
-}
diff --git a/tests/Objects/ExamplesContextQuery.php b/tests/Objects/ExamplesContextQuery.php
index c051ec81..df2cc10c 100644
--- a/tests/Objects/ExamplesContextQuery.php
+++ b/tests/Objects/ExamplesContextQuery.php
@@ -1,7 +1,7 @@
 <?php
 
-use GraphQL\Type\Definition\Type;
 use Folklore\GraphQL\Support\Query;
+use GraphQL\Type\Definition\ResolveInfo;
 
 class ExamplesContextQuery extends Query
 {
@@ -15,7 +15,7 @@ public function type()
         return GraphQL::type('Example');
     }
 
-    public function resolve($root, $args, $context)
+    public function resolve($root, $args, $context, ResolveInfo $info)
     {
         return $context;
     }
diff --git a/tests/Objects/ExamplesPaginationQuery.php b/tests/Objects/ExamplesPaginationQuery.php
index b25dff41..24d98c67 100644
--- a/tests/Objects/ExamplesPaginationQuery.php
+++ b/tests/Objects/ExamplesPaginationQuery.php
@@ -1,7 +1,8 @@
 <?php
 
-use GraphQL\Type\Definition\Type;
 use Folklore\GraphQL\Support\Query;
+use GraphQL\Type\Definition\ResolveInfo;
+use GraphQL\Type\Definition\Type;
 use Illuminate\Pagination\LengthAwarePaginator;
 
 class ExamplesPaginationQuery extends Query
@@ -27,7 +28,7 @@ public function args()
         ];
     }
 
-    public function resolve($root, $args)
+    public function resolve($root, $args, $context, ResolveInfo $info)
     {
         $data = include(__DIR__.'/data.php');
 
diff --git a/tests/Objects/ExamplesQuery.php b/tests/Objects/ExamplesQuery.php
index 2782acb8..7d5b6d99 100644
--- a/tests/Objects/ExamplesQuery.php
+++ b/tests/Objects/ExamplesQuery.php
@@ -1,7 +1,8 @@
 <?php
 
-use GraphQL\Type\Definition\Type;
 use Folklore\GraphQL\Support\Query;
+use GraphQL\Type\Definition\ResolveInfo;
+use GraphQL\Type\Definition\Type;
 
 class ExamplesQuery extends Query
 {
@@ -17,13 +18,17 @@ public function type()
     public function args()
     {
         return [
-            'index' => ['name' => 'index', 'type' => Type::int()]
+            'index' => [
+                'name' => 'index',
+                'type' => Type::int(),
+                'rules' => ['integer', 'max:100']
+            ]
         ];
     }
 
-    public function resolve($root, $args)
+    public function resolve($root, $args, $context, ResolveInfo $info)
     {
-        $data = include(__DIR__.'/data.php');
+        $data = include(__DIR__ . '/data.php');
 
         if (isset($args['index'])) {
             return [
diff --git a/tests/Objects/ExamplesRootQuery.php b/tests/Objects/ExamplesRootQuery.php
index 730673af..f12f1a90 100644
--- a/tests/Objects/ExamplesRootQuery.php
+++ b/tests/Objects/ExamplesRootQuery.php
@@ -1,7 +1,7 @@
 <?php
 
-use GraphQL\Type\Definition\Type;
 use Folklore\GraphQL\Support\Query;
+use GraphQL\Type\Definition\ResolveInfo;
 
 class ExamplesRootQuery extends Query
 {
@@ -15,7 +15,7 @@ public function type()
         return GraphQL::type('Example');
     }
 
-    public function resolve($root, $args, $context)
+    public function resolve($root, $args, $context, ResolveInfo $info)
     {
         return $root;
     }
diff --git a/tests/Objects/UpdateExampleMutation.php b/tests/Objects/UpdateExampleMutation.php
deleted file mode 100644
index a98f4959..00000000
--- a/tests/Objects/UpdateExampleMutation.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-use Folklore\GraphQL\Support\Facades\GraphQL;
-use Folklore\GraphQL\Support\Mutation;
-use GraphQL\Type\Definition\Type;
-
-class UpdateExampleMutation extends Mutation
-{
-
-    protected $attributes = [
-        'name' => 'updateExample'
-    ];
-
-    public function type()
-    {
-        return GraphQL::type('Example');
-    }
-
-    public function rules()
-    {
-        return [
-            'test' => ['required']
-        ];
-    }
-
-    public function args()
-    {
-        return [
-            'test' => [
-                'name' => 'test',
-                'type' => Type::string()
-            ],
-
-            'test_with_rules' => [
-                'name' => 'test',
-                'type' => Type::string(),
-                'rules' => ['required']
-            ],
-
-            'test_with_rules_closure' => [
-                'name' => 'test',
-                'type' => Type::string(),
-                'rules' => function () {
-                    return ['required'];
-                }
-            ],
-        ];
-    }
-
-    public function resolve($root, $args)
-    {
-        return [
-            'test' => array_get($args, 'test')
-        ];
-    }
-}
diff --git a/tests/Objects/UpdateExampleMutationWithInputType.php b/tests/Objects/UpdateExampleMutationWithInputType.php
deleted file mode 100644
index 2f90a185..00000000
--- a/tests/Objects/UpdateExampleMutationWithInputType.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-
-use Folklore\GraphQL\Support\Facades\GraphQL;
-use Folklore\GraphQL\Support\Mutation;
-use GraphQL\Type\Definition\Type;
-
-class UpdateExampleMutationWithInputType extends Mutation
-{
-    protected $attributes = [
-        'name' => 'updateExample',
-    ];
-
-    public function type()
-    {
-        return GraphQL::type('Example');
-    }
-
-    public function rules()
-    {
-        return [
-            'test' => ['required'],
-        ];
-    }
-
-    public function validationErrorMessages($root, $args, $context)
-    {
-        $inavlidEmail = array_get($args, 'test_with_rules_input_object.nest.email');
-
-        return [
-             'test.required' => 'A test is required.',
-             'test_with_rules_input_object.nest.email.email' => 'Invalid your email : '.$inavlidEmail,
-          ];
-    }
-
-    public function args()
-    {
-        return [
-            'test' => [
-                'name' => 'test',
-                'type' => Type::string(),
-            ],
-
-            'test_with_rules' => [
-                'name' => 'test',
-                'type' => Type::string(),
-                'rules' => ['required'],
-            ],
-
-            'test_with_rules_closure' => [
-                'name' => 'test',
-                'type' => Type::string(),
-                'rules' => function () {
-                    return ['required'];
-                },
-            ],
-
-            'test_with_rules_input_object' => [
-                'name' => 'test',
-                'type' => GraphQL::type('ExampleValidationInputObject'),
-                'rules' => ['required'],
-            ],
-        ];
-    }
-
-    public function resolve($root, $args)
-    {
-        return [
-            'test' => array_get($args, 'test'),
-        ];
-    }
-}
diff --git a/tests/Objects/queries.php b/tests/Objects/queries.php
index 0bcebcb2..4554cd5e 100644
--- a/tests/Objects/queries.php
+++ b/tests/Objects/queries.php
@@ -75,15 +75,15 @@
 
     'examplesWithValidation' =>  "
         query QueryExamplesWithValidation(\$index: Int) {
-            examples {
-                test_validation(index: \$index)
+            examples(index: \$index) {
+                test
             }
         }
     ",
 
-    'updateExampleCustom' =>  "
-        mutation UpdateExampleCustom(\$test: String) {
-            updateExampleCustom(test: \$test) {
+    'exampleMutation' => "
+        mutation ExampleMutation(\$required: String) {
+            exampleMutation(required: \$required) {
                 test
             }
         }
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 3ac5406b..7cdddf82 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -30,7 +30,7 @@ protected function getEnvironmentSetUp($app)
                 'examplesPagination' => ExamplesPaginationQuery::class,
             ],
             'mutation' => [
-                'updateExample' => UpdateExampleMutation::class
+                'exampleMutation' => ExampleMutation::class
             ]
         ]);
 
@@ -39,12 +39,14 @@ protected function getEnvironmentSetUp($app)
                 'examplesCustom' => ExamplesQuery::class,
             ],
             'mutation' => [
-                'updateExampleCustom' => UpdateExampleMutation::class
+                'exampleMutationCustom' => ExampleMutation::class
             ]
         ]);
 
         $app['config']->set('graphql.types', [
-            'Example' => ExampleType::class
+            'Example' => ExampleType::class,
+            'ExampleParentInputObject' => ExampleParentInputObject::class,
+            'ExampleChildInputObject' => ExampleChildInputObject::class,
         ]);
     }
 

From 2f0e41aa017088ba8d167616c669e0525dd3a35b Mon Sep 17 00:00:00 2001
From: spawnia <benedikt@franke.tech>
Date: Wed, 16 May 2018 10:29:34 +0200
Subject: [PATCH 2/6] Change expectException method call back to annotation

---
 tests/MutationTest.php | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/tests/MutationTest.php b/tests/MutationTest.php
index 112b03a5..ee9e4f91 100644
--- a/tests/MutationTest.php
+++ b/tests/MutationTest.php
@@ -53,7 +53,7 @@ public function testGetRules()
 
     /**
      * Test resolve throw validation error.
-     *
+     * @expectedException \Folklore\GraphQL\Error\ValidationError
      * @test
      */
     public function testResolveThrowValidationError()
@@ -61,7 +61,6 @@ public function testResolveThrowValidationError()
         $field = $this->getFieldInstance();
         $attributes = $field->getAttributes();
 
-        $this->expectException('\Folklore\GraphQL\Error\ValidationError');
         $attributes['resolve'](null, [
             'email_inline_rules' => 'not-an-email'
         ], [], null);

From 6a2788514c9d2a2521d1b3d24cda0a4153504887 Mon Sep 17 00:00:00 2001
From: spawnia <benedikt@franke.tech>
Date: Wed, 16 May 2018 10:52:23 +0200
Subject: [PATCH 3/6] Sort rules array before assertion to workaround PHPUnit 4
 incompatiblity

---
 tests/MutationTest.php | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/tests/MutationTest.php b/tests/MutationTest.php
index ee9e4f91..945cdc85 100644
--- a/tests/MutationTest.php
+++ b/tests/MutationTest.php
@@ -103,14 +103,16 @@ public function testValidationErrorMessages()
             /** @var \Illuminate\Support\MessageBag $messages */
             $messages = $e->getValidatorMessages();
             $messageKeys = $messages->keys();
-            // Ensure that validation errors occurred only where necessary, ignoring the order
-            $this->assertEquals([
+            $expectedKeys = [
                 'email_inline_rules',
                 'email_list.0',
                 'email_list_of_lists.1.0',
                 'input_object.child.email',
                 'input_object.self.self.alpha',
-            ], $messageKeys, 'Not all the right fields were validated.', 0, 10, true);
+            ];
+            // Ensure that validation errors occurred only where necessary
+            // Sort the arrays before comparison so that order does not matter
+            $this->assertEquals(sort($expectedKeys), sort($messageKeys), 'Not all the right fields were validated.');
 
             // The custom validation error message should override the default
             $this->assertEquals('Has to be a valid email.', $messages->first('email_inline_rules'));

From 467f790d6a5b58d792590119c074e2ed575e0473 Mon Sep 17 00:00:00 2001
From: spawnia <benedikt@franke.tech>
Date: Fri, 18 May 2018 10:04:57 +0200
Subject: [PATCH 4/6] Add type hints to ValidationError

---
 .../GraphQL/Error/ValidationError.php         | 25 ++++++++++++++-----
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/src/Folklore/GraphQL/Error/ValidationError.php b/src/Folklore/GraphQL/Error/ValidationError.php
index b87a50b8..184ce506 100644
--- a/src/Folklore/GraphQL/Error/ValidationError.php
+++ b/src/Folklore/GraphQL/Error/ValidationError.php
@@ -1,26 +1,39 @@
 <?php namespace Folklore\GraphQL\Error;
 
 use GraphQL\Error\Error;
-use GraphQL\FormattedError;
+use Illuminate\Validation\Validator;
 
 class ValidationError extends Error
 {
+    /**
+     * @var Validator
+     */
     public $validator;
-    
-    public function setValidator($validator)
+
+    /**
+     * @param Validator $validator
+     * @return $this
+     */
+    public function setValidator(Validator $validator)
     {
         $this->validator = $validator;
         
         return $this;
     }
-    
+
+    /**
+     * @return Validator
+     */
     public function getValidator()
     {
         return $this->validator;
     }
-    
+
+    /**
+     * @return array|\Illuminate\Support\MessageBag
+     */
     public function getValidatorMessages()
     {
-        return $this->validator ? $this->validator->messages():[];
+        return $this->validator ? $this->validator->messages() : [];
     }
 }

From 364f051a55729c1e8f8b217f66cdfd5b79e9459b Mon Sep 17 00:00:00 2001
From: spawnia <benedikt@franke.tech>
Date: Fri, 18 May 2018 10:19:58 +0200
Subject: [PATCH 5/6] Rework validation to create only minimal necessary rules

---
 Readme.md                                  | 145 ++++++++----
 src/Folklore/GraphQL/Support/Field.php     | 261 +++++++++++----------
 tests/MutationTest.php                     | 199 ++++++++++------
 tests/Objects/ExampleMutation.php          |  15 +-
 tests/Objects/ExampleParentInputObject.php |   4 +-
 5 files changed, 386 insertions(+), 238 deletions(-)

diff --git a/Readme.md b/Readme.md
index 0694edcc..30b2c21c 100644
--- a/Readme.md
+++ b/Readme.md
@@ -429,12 +429,12 @@ reflected through the schema and are validated by the underlying GraphQL impleme
 
 #### Rule definition
 
-You can add validation rules directly to the arguments of Mutations or Queries:
+##### Inline Array
 
+The preferred way to add rules is to inline them with the arguments of Mutations or Queries.
 
 ```php
 //...
-
 class UpdateUserEmailMutation extends Mutation
 {
     //...
@@ -442,37 +442,82 @@ class UpdateUserEmailMutation extends Mutation
     public function args()
         {
             return [
-                'id' => [
-                    'name' => 'id',
-                    'type' => Type::nonNull(Type::string()),
-                    // Adding a rule for 'required' is not necessary
-                ],
                 'email' => [
                     'name' => 'email',
-                    'type' => Type::nonNull(Type::string()),
-                    'rules' => ['email']
-                ],
-                'links' => [
-                    'name' => 'links',
-                    'type' => Type::listOf(Type::string()),
-                    // Validation is applied to each element of the list 
-                    'rules' => ['url']
-                ],
-                'name' => [
-                    'name' => 'name',
-                    'type' => GraphQL::type('NameInputObject')
+                    'type' => Type::string(),
+                    'rules' => [
+                        'email',
+                        'exists:users,email'
+                    ]
                 ],
             ];
         }
-
-    //...
 }
 ```
 
+##### Inline Closure
+
+Rules may also be defined as closures. They are called before the resolve function of the field is called
+and receive the same arguments.
+
+````php
+'phone' => [
+    'name' => 'phone',
+    'type' => Type::nonNull(Type::string()),
+    'rules' => function ($root, $args, $context, \GraphQL\Type\Definition\ResolveInfo $resolveInfo){
+        return [];
+    }
+],
+````
+
+##### Rule Overwrites
+
+You can overwrite inline rules of fields or nested Input Objects by defining them like this:
+
+````php
+public function rules()
+{
+    return [
+        'email' => ['email', 'min:10'],
+        'nested.value' => ['alpha_num'],
+    ];
+}
+````
+
+Be aware that those rules are always applied, even if the argument is not given. You may want to prefix
+them with `sometimes` if the rule is optional.
+
+#### Required Arguments
+
+GraphQL has a built-in way of defining arguments as required, simply wrap them in a `Type::nonNull()`.
+
+````php
+'id' => [
+    'name' => 'id',
+    'type' => Type::nonNull(Type::string()),
+],
+````
+
+The presence of such arguments is checked before the arguments even reach the resolver, so there is
+no need to validate them through an additional rule, so you will not ever need `required`.
+Defining required arguments through the Non-Null type is preferable because it shows up in the schema definition. 
+
+Because GraphQL arguments are optional by default, the validation rules for them will only be applied if they are present.
+If you need more sophisticated validation of fields, using additional rules like `required_with` is fine.
+
 #### Input Object Rules
 
-Notice how the argument `name` has the type `NameInputObject`? The rules defined in Input Objects are also considered when
-validating the Mutation! The definition of those rules looks like this:
+You may use Input Objects as arguments like this:
+
+````php
+'name' => [
+    'name' => 'name',
+    'type' => GraphQL::type('NameInputObject')
+],
+````
+
+Rules defined in the Input Object are automatically added to the validation, even if nested Input Objects are used.
+The definition of those rules looks like this:
 
 ````php
 <?php
@@ -506,12 +551,46 @@ class NameInputObject extends BaseType
 }
 ```` 
 
-Now, the rules in here ensure that if a name is passed to the above Mutation, it must contain at least a
+Now, the rules in here ensure that if a name is passed to base field, it must contain at least a
 last name, and the first and last name can only contain alphabetic characters.
 
+#### Array Validation
+
+GraphQL allows arguments to be defined as lists by wrapping them in `Type::listOf()`.
+In most cases it is desirable to apply validation rules to the underlying elements of the array.
+If a type is wrapped as a list, the inline rules are automatically applied to the underlying
+elements.
+
+````php
+'links' => [
+    'name' => 'links',
+    'type' => Type::listOf(Type::string()), 
+    'rules' => ['url', 'distinct'],
+],
+````
+
+If validation on the array itself is required, you can do so by defining those rules seperately:
+
+````php
+public function rules()
+{
+    return [
+        'links' => ['max:10']
+    ];
+}
+```` 
+
+This ensures that `links` is an array of at most 10, distinct urls.
+
 #### Response format
 
-When you execute a mutation, it will return the validation errors. Since the GraphQL specification defines a certain format for errors, the validation error messages are added to the error object as an extra `validation` attribute. To find the validation error, you should check for the error with a `message` equals to `'validation'`, then the `validation` attribute will contain the normal errors messages returned by the Laravel Validator.
+When you execute a field with arguments, it will return the validation errors.
+Since the GraphQL specification defines a certain format for errors, the validation error messages
+are added to the error object as an extra `validation` attribute.
+
+To find the validation error, you should check for the error with a `message`
+equals to `'validation'`, then the `validation` attribute will contain the normal
+errors messages returned by the Laravel Validator.
 
 ```json
 {
@@ -536,19 +615,3 @@ When you execute a mutation, it will return the validation errors. Since the Gra
   ]
 }
 ```
-
-#### Validation depth
-
-Laravel validation works by statically generating validation rules and then applying
-them to arguments all at once. However, field arguments may consist of Input Objects, which
-could have circular references. Because of this, the maximum depth for validation has to be
-set before. The default depth is set to `10`, it can be overwritten this way:
-
-````php
-class ExampleMutation extends Mutation
-{
-    protected $validationDepth = 15;
-    
-    //...
-}
-````
diff --git a/src/Folklore/GraphQL/Support/Field.php b/src/Folklore/GraphQL/Support/Field.php
index 79f90fc2..01898d57 100644
--- a/src/Folklore/GraphQL/Support/Field.php
+++ b/src/Folklore/GraphQL/Support/Field.php
@@ -4,13 +4,12 @@
 
 use Folklore\GraphQL\Error\AuthorizationError;
 use Folklore\GraphQL\Error\ValidationError;
-use GraphQL\Type\Definition\InputObjectField;
 use GraphQL\Type\Definition\InputObjectType;
 use GraphQL\Type\Definition\ListOfType;
 use GraphQL\Type\Definition\ResolveInfo;
 use GraphQL\Type\Definition\WrappingType;
-use Illuminate\Contracts\Validation\Validator;
 use Illuminate\Support\Fluent;
+use Illuminate\Validation\Validator;
 
 /**
  * This is a generic class for fields.
@@ -23,20 +22,6 @@
  */
 abstract class Field extends Fluent
 {
-    /**
-     * Validation rules for the Field arguments.
-     *
-     * @var array
-     */
-    protected $rules = [];
-
-    /**
-     * How many levels of nesting should validation be applied to.
-     *
-     * @var int
-     */
-    protected $validationDepth = 10;
-
     /**
      * Override this in your queries or mutations to provide custom authorization.
      *
@@ -110,22 +95,6 @@ public function args()
      */
     public abstract function type();
 
-    /**
-     * Return a result for the field.
-     *
-     * @param $root
-     * @param $args
-     * @param $context
-     * @param ResolveInfo $info
-     * @return mixed
-     */
-    public function resolve($root, $args, $context, ResolveInfo $info)
-    {
-        // Use the default resolver, can be overridden in custom Fields, Queries or Mutations
-        // This enables us to still apply authentication, authorization and validation upon the query
-        return call_user_func(config('graphql.defaultFieldResolver'));
-    }
-
     /**
      * Returns a function that wraps the resolve function with authentication and validation checks.
      *
@@ -133,54 +102,40 @@ public function resolve($root, $args, $context, ResolveInfo $info)
      */
     protected function getResolver()
     {
-        $authenticated = [$this, 'authenticated'];
-        $authorize = [$this, 'authorize'];
-        $resolve = [$this, 'resolve'];
-
-        return function () use ($authenticated, $authorize, $resolve) {
-            $args = func_get_args();
+        return function () {
+            $resolutionArguments = func_get_args();
 
             // Check authentication first
-            if (call_user_func_array($authenticated, $args) !== true) {
+            if (call_user_func_array([$this, 'authenticated'], $resolutionArguments) !== true) {
                 throw new AuthorizationError('Unauthenticated');
             }
 
             // After authentication, check specific authorization
-            if (call_user_func_array($authorize, $args) !== true) {
+            if (call_user_func_array([$this, 'authorize'], $resolutionArguments) !== true) {
                 throw new AuthorizationError('Unauthorized');
             }
 
-            // Apply additional validation
-            $rules = call_user_func_array([$this, 'getRules'], $args);
-            if (sizeof($rules)) {
-                $validationErrorMessages = call_user_func_array([$this, 'validationErrorMessages'], $args);
-                $inputArguments = array_get($args, 1, []);
-                $validator = $this->getValidator($inputArguments, $rules, $validationErrorMessages);
-                if ($validator->fails()) {
-                    throw with(new ValidationError('validation'))->setValidator($validator);
-                }
-            }
+            call_user_func_array([$this, 'validate'], $resolutionArguments);
+
 
-            return call_user_func_array($resolve, $args);
+            return call_user_func_array([$this, 'resolve'], $resolutionArguments);
         };
     }
 
     /**
-     * @param $args
-     * @param $rules
-     * @param array $messages
+     * Return a result for the field.
      *
-     * @return Validator
+     * @param $root
+     * @param $args
+     * @param $context
+     * @param ResolveInfo $info
+     * @return mixed
      */
-    protected function getValidator($args, $rules, $messages = [])
+    public function resolve($root, $args, $context, ResolveInfo $info)
     {
-        /** @var Validator $validator */
-        $validator = app('validator')->make($args, $rules, $messages);
-        if (method_exists($this, 'withValidator')) {
-            $this->withValidator($validator, $args);
-        }
-
-        return $validator;
+        // Use the default resolver, can be overridden in custom Fields, Queries or Mutations
+        // This enables us to still apply authentication, authorization and validation upon the query
+        return call_user_func(config('graphql.defaultFieldResolver'));
     }
 
     /**
@@ -207,87 +162,142 @@ public function __isset($key)
         return isset($attributes[$key]);
     }
 
+    /**
+     * Overwrite rules at any part in the tree of field arguments.
+     *
+     * The rules defined in here take precedence over the rules that are
+     * defined inline or inferred from nested Input Objects.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [];
+    }
+
+    /**
+     * Gather all the rules and throw if invalid.
+     */
+    protected function validate()
+    {
+        $resolutionArguments = func_get_args();
+        $inputArguments = array_get($resolutionArguments, 1, []);
+
+        $argumentRules = $this->getRulesForArguments($this->args(), $inputArguments);
+        $explicitRules = call_user_func_array([$this, 'rules'], $resolutionArguments);
+        $argumentRules = array_merge($argumentRules, $explicitRules);
+
+        foreach ($argumentRules as $key => $rules) {
+            $resolvedRules[$key] = $this->resolveRules($rules, $resolutionArguments);
+        }
+
+        if (isset($resolvedRules)) {
+            $validationErrorMessages = call_user_func_array([$this, 'validationErrorMessages'], $resolutionArguments);
+            $validator = $this->getValidator($inputArguments, $resolvedRules, $validationErrorMessages);
+            if ($validator->fails()) {
+                throw (new ValidationError('validation'))->setValidator($validator);
+            }
+        }
+    }
+
     /**
      * Get the combined explicit and type-inferred rules.
      *
+     * @param $argDefinitions
+     * @param $argValues
      * @return array
      */
-    public function getRules()
+    protected function getRulesForArguments($argDefinitions, $argValues)
     {
-        $arguments = func_get_args();
+        $rules = [];
+
+        foreach ($argValues as $name => $value) {
+            $definition = $argDefinitions[$name];
 
-        $argsRules = $this->getRulesFromArgs($this->args(), $this->validationDepth, null, $arguments);
+            $typeAndKey = $this->unwrapType($definition['type'], $name);
+            $key = $typeAndKey['key'];
+            $type = $typeAndKey['type'];
+
+            // Get rules that are directly defined on the field
+            if (isset($definition['rules'])) {
+                $rules[$key] = $definition['rules'];
+            }
+
+            if ($type instanceof InputObjectType) {
+                $rules = array_merge($rules, $this->getRulesFromInputObjectType($key, $type, $value));
+            }
+        }
 
-        // Merge rules that were set separately with those defined through args
-        $explicitRules = call_user_func_array([$this, 'rules'], $arguments);
-        return array_merge($argsRules, $explicitRules);
+        return $rules;
     }
 
     /**
-     * @param $fields
-     * @param $inputValueDepth
-     * @param $parentKey
-     * @param $resolutionArguments
+     * @param $type
+     * @param $key
      * @return array
      */
-    protected function getRulesFromArgs($fields, $inputValueDepth, $parentKey, $resolutionArguments)
+    protected function unwrapType($type, $key)
     {
-        // At depth 0, there are still field rules to gather
-        // once we get below 0, we have gathered all the rules necessary
-        // for the nesting depth
-        if ($inputValueDepth < 0) {
-            return [];
+        // Unpack until we get to the root type
+        while ($type instanceof WrappingType) {
+            if ($type instanceof ListOfType) {
+                // Add this to the prefix to allow Laravel validation for arrays
+                $key .= '.*';
+            }
+
+            $type = $type->getWrappedType();
         }
 
-        // We are going one level deeper
-        $inputValueDepth--;
+        return [
+            'type' => $type,
+            'key' => $key,
+        ];
+    }
 
+    protected function getRulesFromInputObjectType($parentKey, InputObjectType $inputObject, $values)
+    {
         $rules = [];
-        // Merge in the rules of the Input Type
-        foreach ($fields as $fieldName => $field) {
-            // Count the depth per field
-            $fieldDepth = $inputValueDepth;
-
-            // If given, add the parent key
-            $key = $parentKey ? "{$parentKey}.{$fieldName}" : $fieldName;
-
-            // The values passed in here may be of different types, depending on where they were defined
-            if ($field instanceof InputObjectField) {
-                // We can depend on type being set, since a field without type is not valid
-                $type = $field->type;
-                // Rules are optional so they may not be set
-                $fieldRules = isset($field->rules) ? $field->rules : [];
-            } else {
-                $type = $field['type'];
-                $fieldRules = isset($field['rules']) ? $field['rules'] : [];
-            }
+        // At this point we know we expect InputObjects, but there might be more then one value
+        // Since they might have different fields, we have to look at each of them individually
+        // If we have string keys, we are dealing with the values themselves
+        if ($this->hasStringKeys($values)) {
+            foreach ($values as $name => $value) {
+                $key = "{$parentKey}.{$name}";
 
-            // Unpack until we get to the root type
-            while ($type instanceof WrappingType) {
-                if ($type instanceof ListOfType) {
-                    // This lets us skip one level of validation rules
-                    $fieldDepth--;
-                    // Add this to the prefix to allow Laravel validation for arrays
-                    $key .= '.*';
-                }
+                $field = $inputObject->getFields()[$name];
 
-                $type = $type->getWrappedType();
-            }
+                $typeAndKey = $this->unwrapType($field->type, $key);
+                $key = $typeAndKey['key'];
+                $type = $typeAndKey['type'];
 
-            // Add explicitly set rules if they apply
-            if (sizeof($fieldRules)) {
-                $rules[$key] = $this->resolveRules($fieldRules, $resolutionArguments);
-            }
+                if (isset($field->rules)) {
+                    $rules[$key] = $field->rules;
+                }
 
-            if ($type instanceof InputObjectType) {
-                // Recursively call the parent method to get nested rules, passing in the new prefix
-                $rules = array_merge($rules, $this->getRulesFromArgs($type->getFields(), $fieldDepth, $key, $resolutionArguments));
+                if ($type instanceof InputObjectType) {
+                    // Recursively call the parent method to get nested rules, passing in the new prefix
+                    $rules = array_merge($rules, $this->getRulesFromInputObjectType($key, $type, $value));
+                }
+            }
+        } else {
+            // Go one level deeper so we deal with actual values
+            foreach ($values as $nestedValues) {
+                $rules = array_merge($rules, $this->getRulesFromInputObjectType($parentKey, $inputObject, $nestedValues));
             }
         }
 
         return $rules;
     }
 
+    /**
+     * @param array $array
+     * @return bool
+     */
+    protected function hasStringKeys(array $array)
+    {
+        return count(array_filter(array_keys($array), 'is_string')) > 0;
+    }
+
     /**
      * @param $rules
      * @param $arguments
@@ -295,7 +305,7 @@ protected function getRulesFromArgs($fields, $inputValueDepth, $parentKey, $reso
      */
     protected function resolveRules($rules, $arguments)
     {
-        // Rules can be defined as closures
+        // Rules can be defined as closures that are passed the resolution arguments
         if (is_callable($rules)) {
             return call_user_func_array($rules, $arguments);
         }
@@ -304,14 +314,21 @@ protected function resolveRules($rules, $arguments)
     }
 
     /**
-     * Can be overwritten to define rules.
+     * @param $args
+     * @param $rules
+     * @param array $messages
      *
-     * @deprecated Will be removed in favour of defining rules together with the args.
-     * @return array
+     * @return Validator
      */
-    protected function rules()
+    protected function getValidator($args, $rules, $messages = [])
     {
-        return [];
+        /** @var Validator $validator */
+        $validator = app('validator')->make($args, $rules, $messages);
+        if (method_exists($this, 'withValidator')) {
+            $this->withValidator($validator, $args);
+        }
+
+        return $validator;
     }
 
     /**
diff --git a/tests/MutationTest.php b/tests/MutationTest.php
index 945cdc85..ad8d5b35 100644
--- a/tests/MutationTest.php
+++ b/tests/MutationTest.php
@@ -1,7 +1,5 @@
 <?php
 
-use Illuminate\Validation\Validator;
-
 class MutationTest extends FieldTest
 {
     protected function getFieldClass()
@@ -10,80 +8,79 @@ protected function getFieldClass()
     }
 
     /**
-     * Laravel based validation rules should be correctly constructed.
+     * Test resolve throw validation error.
      *
+     * @expectedException \Folklore\GraphQL\Error\ValidationError
      * @test
      */
-    public function testGetRules()
+    public function testResolveThrowValidationError()
     {
-        $field = $this->getFieldInstance();
-        $rules = $field->getRules();
-
-        $this->assertInternalType('array', $rules);
-
-        // Those three definitions must have the same result
-        $this->assertEquals(['email'], $rules['email_seperate_rules']);
-        $this->assertEquals(['email'], $rules['email_inline_rules']);
-        $this->assertEquals(['email'], $rules['email_closure_rules']);
-
-        // Apply array validation to values defined as GraphQL-Lists
-        $this->assertEquals(['email'], $rules['email_list.*']);
-        $this->assertEquals(['email'], $rules['email_list_of_lists.*.*']);
-
-        // Inferred rules from Input Objects
-        $this->assertEquals(['alpha'], $rules['input_object.alpha']);
-        $this->assertEquals(['email'], $rules['input_object.child.email']);
-        $this->assertEquals(['email'], $rules['input_object.child-list.*.email']);
-
-        // Self-referencing, nested InputObject
-        $this->assertEquals(['alpha'], $rules['input_object.self.alpha']);
-        $this->assertEquals(['email'], $rules['input_object.self.child.email']);
-        $this->assertEquals(['email'], $rules['input_object.self.child-list.*.email']);
-
-        // Go down a few levels
-        $this->assertEquals(['alpha'], $rules['input_object.self.self.self.self.alpha']);
-        $this->assertEquals(['email'], $rules['input_object.self.self.self.self.child.email']);
-        $this->assertEquals(['email'], $rules['input_object.self.self.self.self.child-list.*.email']);
-
-        $this->assertArrayNotHasKey(
-            'input_object.self.self.self.self.self.self.self.self.self.self.alpha',
-            $rules,
-            'Validation rules should not be set for such deep nesting.');
+        $this->callResolveWithInput([
+            'email_inline_rules' => 'not-an-email'
+        ]);
     }
 
     /**
-     * Test resolve throw validation error.
-     * @expectedException \Folklore\GraphQL\Error\ValidationError
+     * Validation error messages are correctly constructed and thrown.
+     *
      * @test
      */
-    public function testResolveThrowValidationError()
+    public function testCustomValidationErrorMessages()
     {
-        $field = $this->getFieldInstance();
-        $attributes = $field->getAttributes();
+        try {
+            $this->callResolveWithInput([
+                'email_inline_rules' => 'not-an-email',
+                'input_object' => [
+                    'child' => [
+                        'email' => 'not-an-email'
+                    ],
+                ]
+            ]);
+        } catch (\Folklore\GraphQL\Error\ValidationError $e) {
+            $messages = $e->getValidatorMessages();
 
-        $attributes['resolve'](null, [
-            'email_inline_rules' => 'not-an-email'
-        ], [], null);
+            // The custom validation error message should override the default
+            $this->assertEquals('Has to be a valid email.', $messages->first('email_inline_rules'));
+            $this->assertEquals('Invalid email: not-an-email', $messages->first('input_object.child.email'));
+        }
     }
 
     /**
-     * Validation error messages are correctly constructed and thrown.
-     *
      * @test
      */
-    public function testValidationErrorMessages()
+    public function testArrayValidationIsApplied()
     {
-        $field = $this->getFieldInstance();
-        $attributes = $field->getAttributes();
-
         try {
-            $attributes['resolve'](null, [
-                'email_inline_rules' => 'not-an-email',
+            $this->callResolveWithInput([
                 'email_list' => ['not-an-email', 'valid@email.com'],
                 'email_list_of_lists' => [
                     ['valid@email.com'],
                     ['not-an-email'],
                 ],
+            ]);
+        } catch (\Folklore\GraphQL\Error\ValidationError $e) {
+            $messages = $e->getValidatorMessages();
+
+            $messageKeys = $messages->keys();
+            $expectedKeys = [
+                'email_list.0',
+                'email_list_of_lists.1.0',
+            ];
+            // Sort the arrays before comparison so that order does not matter
+            sort($expectedKeys);
+            sort($messageKeys);
+            // Ensure that validation errors occurred only where necessary
+            $this->assertEquals($expectedKeys, $messageKeys, 'Not all the right fields were validated.');
+        }
+    }
+
+    /**
+     * @test
+     */
+    public function testRulesForNestedInputObjects()
+    {
+        try {
+            $this->callResolveWithInput([
                 'input_object' => [
                     'child' => [
                         'email' => 'not-an-email'
@@ -91,33 +88,95 @@ public function testValidationErrorMessages()
                     'self' => [
                         'self' => [
                             'alpha' => 'Not alphanumeric !"§)'
+                        ],
+                        'child_list' => [
+                            ['email' => 'abc'],
+                            ['email' => 'def']
                         ]
                     ]
                 ]
-            ], [], null);
+            ]);
         } catch (\Folklore\GraphQL\Error\ValidationError $e) {
             $validator = $e->getValidator();
+            $rules = $validator->getRules();
 
-            $this->assertInstanceOf(Validator::class, $validator);
+            $this->assertEquals(['email'], $rules['input_object.child.email']);
+            $this->assertEquals(['alpha'], $rules['input_object.self.self.alpha']);
+            $this->assertEquals(['email'], $rules['input_object.self.child_list.0.email']);
+            $this->assertEquals(['email'], $rules['input_object.self.child_list.1.email']);
+        }
+    }
+
+    /**
+     * @test
+     */
+    public function testExplicitRulesOverwriteInlineRules()
+    {
+        try {
+            $this->callResolveWithInput([
+                'email_seperate_rules' => 'asdf'
+            ]);
+        } catch (\Folklore\GraphQL\Error\ValidationError $e) {
+            $validator = $e->getValidator();
+            $rules = $validator->getRules();
+
+            $this->assertEquals(['email'], $rules['email_seperate_rules']);
+        }
+    }
+
+    /**
+     * @test
+     */
+    public function testCanValidateArraysThroughSeperateRules()
+    {
+        try {
+            $this->callResolveWithInput([
+                'email_list' => [
+                    'invalid',
+                    'asdf@asdf.de',
+                    'asdf@asdf.de',
+                ]
+            ]);
+        } catch (\Folklore\GraphQL\Error\ValidationError $e) {
+            $validator = $e->getValidator();
+            $rules = $validator->getRules();
+
+            $this->assertEquals(['max:2'], $rules['email_list']);
+            $this->assertEquals(['email'], $rules['email_list.0']);
+            $this->assertEquals(['email'], $rules['email_list.1']);
+            $this->assertEquals(['email'], $rules['email_list.2']);
 
-            /** @var \Illuminate\Support\MessageBag $messages */
             $messages = $e->getValidatorMessages();
-            $messageKeys = $messages->keys();
-            $expectedKeys = [
-                'email_inline_rules',
-                'email_list.0',
-                'email_list_of_lists.1.0',
-                'input_object.child.email',
-                'input_object.self.self.alpha',
-            ];
-            // Ensure that validation errors occurred only where necessary
-            // Sort the arrays before comparison so that order does not matter
-            $this->assertEquals(sort($expectedKeys), sort($messageKeys), 'Not all the right fields were validated.');
+            $this->assertEquals('The email list may not be greater than 2 characters.', $messages->first('email_list'));
+            $this->assertEquals('The email_list.0 must be a valid email address.', $messages->first('email_list.0'));
+        }
+    }
 
-            // The custom validation error message should override the default
-            $this->assertEquals('Has to be a valid email.', $messages->first('email_inline_rules'));
-            $this->assertEquals('Invalid email: not-an-email', $messages->first('input_object.child.email'));
+    /**
+     * @test
+     */
+    public function testRequiredWithRule()
+    {
+        try {
+            $this->callResolveWithInput([
+                'required' => 'whatever'
+            ]);
+        } catch (\Folklore\GraphQL\Error\ValidationError $e) {
+            $validator = $e->getValidator();
+            $rules = $validator->getRules();
+
+            $this->assertEquals(['required_with:required'], $rules['required_with']);
 
+            $messages = $e->getValidatorMessages();
+            $this->assertEquals('The required with field is required when required is present.', $messages->first('required_with'));
         }
     }
+
+    protected function callResolveWithInput($input)
+    {
+        $field = $this->getFieldInstance();
+        $attributes = $field->getAttributes();
+
+        $attributes['resolve'](null, $input, [], new \GraphQL\Type\Definition\ResolveInfo([]));
+    }
 }
diff --git a/tests/Objects/ExampleMutation.php b/tests/Objects/ExampleMutation.php
index 1b6c28fc..f4c785e2 100644
--- a/tests/Objects/ExampleMutation.php
+++ b/tests/Objects/ExampleMutation.php
@@ -27,9 +27,16 @@ public function args()
                 'type' => Type::nonNull(Type::string()),
             ],
 
+            'required_with' => [
+                'name' => 'required_with',
+                'type' => Type::string(),
+            ],
+
             'email_seperate_rules' => [
                 'name' => 'email_seperate_rules',
-                'type' => Type::string()
+                'type' => Type::string(),
+                // Should be overwritten by those defined in rules()
+                'rules' => ['integer'],
             ],
 
             'email_inline_rules' => [
@@ -72,10 +79,12 @@ public function resolve($root, $args, $context, ResolveInfo $info)
         ];
     }
 
-    protected function rules()
+    public function rules()
     {
         return [
-            'email_seperate_rules' => ['email']
+            'email_seperate_rules' => ['email'],
+            'email_list' => ['max:2'],
+            'required_with' => ['required_with:required'],
         ];
     }
 
diff --git a/tests/Objects/ExampleParentInputObject.php b/tests/Objects/ExampleParentInputObject.php
index 89531b1f..a92698f5 100644
--- a/tests/Objects/ExampleParentInputObject.php
+++ b/tests/Objects/ExampleParentInputObject.php
@@ -31,8 +31,8 @@ public function fields()
                 'type' => GraphQL::type('ExampleChildInputObject'),
             ],
 
-            'child-list' => [
-                'name' => 'child-list',
+            'child_list' => [
+                'name' => 'child_list',
                 'type' => Type::listOf(GraphQL::type('ExampleChildInputObject')),
             ],
 

From 02d66fd9c653eceb1ddb69671d86602616262a74 Mon Sep 17 00:00:00 2001
From: spawnia <benedikt@franke.tech>
Date: Fri, 18 May 2018 10:23:30 +0200
Subject: [PATCH 6/6] Add rules function to stubs for generating Fields

---
 src/Folklore/GraphQL/Console/stubs/field.stub    | 13 +++++++++++++
 src/Folklore/GraphQL/Console/stubs/mutation.stub | 13 +++++++++++++
 src/Folklore/GraphQL/Console/stubs/query.stub    | 13 +++++++++++++
 3 files changed, 39 insertions(+)

diff --git a/src/Folklore/GraphQL/Console/stubs/field.stub b/src/Folklore/GraphQL/Console/stubs/field.stub
index 22c96f9f..b0c14e0d 100644
--- a/src/Folklore/GraphQL/Console/stubs/field.stub
+++ b/src/Folklore/GraphQL/Console/stubs/field.stub
@@ -39,6 +39,19 @@ class DummyClass extends Field
         ];
     }
 
+    /**
+     * Overwrite rules at any part in the tree of field arguments.
+     *
+     * The rules defined in here take precedence over the rules that are
+     * defined inline or inferred from nested Input Objects.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [];
+    }
+
     /**
      * Return a result for the field which should match up with its return type.
      *
diff --git a/src/Folklore/GraphQL/Console/stubs/mutation.stub b/src/Folklore/GraphQL/Console/stubs/mutation.stub
index 80619327..d3d0d146 100644
--- a/src/Folklore/GraphQL/Console/stubs/mutation.stub
+++ b/src/Folklore/GraphQL/Console/stubs/mutation.stub
@@ -39,6 +39,19 @@ class DummyClass extends Mutation
         ];
     }
 
+    /**
+     * Overwrite rules at any part in the tree of field arguments.
+     *
+     * The rules defined in here take precedence over the rules that are
+     * defined inline or inferred from nested Input Objects.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [];
+    }
+
     /**
      * Return a result for the field which should match up with its return type.
      *
diff --git a/src/Folklore/GraphQL/Console/stubs/query.stub b/src/Folklore/GraphQL/Console/stubs/query.stub
index 0092d20d..95464db5 100644
--- a/src/Folklore/GraphQL/Console/stubs/query.stub
+++ b/src/Folklore/GraphQL/Console/stubs/query.stub
@@ -39,6 +39,19 @@ class DummyClass extends Query
         ];
     }
 
+    /**
+     * Overwrite rules at any part in the tree of field arguments.
+     *
+     * The rules defined in here take precedence over the rules that are
+     * defined inline or inferred from nested Input Objects.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [];
+    }
+
     /**
      * Return a result for the field which should match up with its return type.
      *