From c596152a28d9850bfe71841109122f2c1d21ef3a Mon Sep 17 00:00:00 2001 From: Kirill Grigorev Date: Mon, 23 Sep 2024 14:22:14 +0200 Subject: [PATCH] validator: make rules exported and allow custom set of rules be passed to the Validate --- validator/imported_test.go | 3 +- validator/rules/fields_on_correct_type.go | 15 ++++--- .../rules/fragments_on_composite_types.go | 13 ++++-- validator/rules/known_argument_names.go | 13 ++++-- validator/rules/known_directives.go | 13 ++++-- validator/rules/known_fragment_names.go | 13 ++++-- validator/rules/known_root_type.go | 13 ++++-- validator/rules/known_type_names.go | 13 ++++-- validator/rules/lone_anonymous_operation.go | 13 ++++-- validator/rules/no_fragment_cycles.go | 13 ++++-- validator/rules/no_undefined_variables.go | 13 ++++-- validator/rules/no_unused_fragments.go | 13 ++++-- validator/rules/no_unused_variables.go | 13 ++++-- .../rules/overlapping_fields_can_be_merged.go | 15 ++++--- .../overlapping_fields_can_be_merged_test.go | 2 +- validator/rules/possible_fragment_spreads.go | 13 ++++-- .../rules/provided_required_arguments.go | 16 +++++--- validator/rules/scalar_leafs.go | 13 ++++-- validator/rules/single_field_subscriptions.go | 13 ++++-- validator/rules/unique_argument_names.go | 13 ++++-- .../rules/unique_directives_per_location.go | 13 ++++-- validator/rules/unique_fragment_names.go | 13 ++++-- validator/rules/unique_input_field_names.go | 13 ++++-- validator/rules/unique_operation_names.go | 13 ++++-- validator/rules/unique_variable_names.go | 13 ++++-- validator/rules/values_of_correct_type.go | 13 ++++-- validator/rules/variables_are_input_types.go | 13 ++++-- .../rules/variables_in_allowed_position.go | 13 ++++-- validator/validator.go | 26 +++++++----- validator/validator_test.go | 41 +++++++++++++++++++ 30 files changed, 296 insertions(+), 121 deletions(-) diff --git a/validator/imported_test.go b/validator/imported_test.go index 41a3eedf..d5f255f5 100644 --- a/validator/imported_test.go +++ b/validator/imported_test.go @@ -11,10 +11,11 @@ import ( "testing" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" + "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/gqlerror" - "gopkg.in/yaml.v2" ) type Spec struct { diff --git a/validator/rules/fields_on_correct_type.go b/validator/rules/fields_on_correct_type.go index 24d4f3db..64ab670e 100644 --- a/validator/rules/fields_on_correct_type.go +++ b/validator/rules/fields_on_correct_type.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "fmt" @@ -11,8 +11,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("FieldsOnCorrectType", func(observers *Events, addError AddErrFunc) { +var FieldsOnCorrectTypeRule = Rule{ + Name: "FieldsOnCorrectType", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnField(func(walker *Walker, field *ast.Field) { if field.ObjectDefinition == nil || field.Definition != nil { return @@ -31,10 +32,14 @@ func init() { At(field.Position), ) }) - }) + }, +} + +func init() { + AddRule(FieldsOnCorrectTypeRule.Name, FieldsOnCorrectTypeRule.RuleFunc) } -// Go through all of the implementations of type, as well as the interfaces +// Go through all the implementations of type, as well as the interfaces // that they implement. If any of those types include the provided field, // suggest them, sorted by how often the type is referenced, starting // with Interfaces. diff --git a/validator/rules/fragments_on_composite_types.go b/validator/rules/fragments_on_composite_types.go index 81ef861b..077bdd60 100644 --- a/validator/rules/fragments_on_composite_types.go +++ b/validator/rules/fragments_on_composite_types.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "fmt" @@ -9,8 +9,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("FragmentsOnCompositeTypes", func(observers *Events, addError AddErrFunc) { +var FragmentsOnCompositeTypesRule = Rule{ + Name: "FragmentsOnCompositeTypes", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnInlineFragment(func(walker *Walker, inlineFragment *ast.InlineFragment) { fragmentType := walker.Schema.Types[inlineFragment.TypeCondition] if fragmentType == nil || fragmentType.IsCompositeType() { @@ -37,5 +38,9 @@ func init() { At(fragment.Position), ) }) - }) + }, +} + +func init() { + AddRule(FragmentsOnCompositeTypesRule.Name, FragmentsOnCompositeTypesRule.RuleFunc) } diff --git a/validator/rules/known_argument_names.go b/validator/rules/known_argument_names.go index c187dabf..2659aeb5 100644 --- a/validator/rules/known_argument_names.go +++ b/validator/rules/known_argument_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownArgumentNames", func(observers *Events, addError AddErrFunc) { +var KnownArgumentNamesRule = Rule{ + Name: "KnownArgumentNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { // A GraphQL field is only valid if all supplied arguments are defined by that field. observers.OnField(func(walker *Walker, field *ast.Field) { if field.Definition == nil || field.ObjectDefinition == nil { @@ -55,5 +56,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(KnownArgumentNamesRule.Name, KnownArgumentNamesRule.RuleFunc) } diff --git a/validator/rules/known_directives.go b/validator/rules/known_directives.go index f7bae811..b68fa5e8 100644 --- a/validator/rules/known_directives.go +++ b/validator/rules/known_directives.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownDirectives", func(observers *Events, addError AddErrFunc) { +var KnownDirectivesRule = Rule{ + Name: "KnownDirectives", + RuleFunc: func(observers *Events, addError AddErrFunc) { type mayNotBeUsedDirective struct { Name string Line int @@ -45,5 +46,9 @@ func init() { seen[tmp] = true } }) - }) + }, +} + +func init() { + AddRule(KnownDirectivesRule.Name, KnownDirectivesRule.RuleFunc) } diff --git a/validator/rules/known_fragment_names.go b/validator/rules/known_fragment_names.go index 3afd9c1c..77a82f67 100644 --- a/validator/rules/known_fragment_names.go +++ b/validator/rules/known_fragment_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownFragmentNames", func(observers *Events, addError AddErrFunc) { +var KnownFragmentNamesRule = Rule{ + Name: "KnownFragmentNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnFragmentSpread(func(walker *Walker, fragmentSpread *ast.FragmentSpread) { if fragmentSpread.Definition == nil { addError( @@ -17,5 +18,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(KnownFragmentNamesRule.Name, KnownFragmentNamesRule.RuleFunc) } diff --git a/validator/rules/known_root_type.go b/validator/rules/known_root_type.go index 60bc0d52..76962c3d 100644 --- a/validator/rules/known_root_type.go +++ b/validator/rules/known_root_type.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "fmt" @@ -9,8 +9,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownRootType", func(observers *Events, addError AddErrFunc) { +var KnownRootTypeRule = Rule{ + Name: "KnownRootType", + RuleFunc: func(observers *Events, addError AddErrFunc) { // A query's root must be a valid type. Surprisingly, this isn't // checked anywhere else! observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { @@ -33,5 +34,9 @@ func init() { At(operation.Position)) } }) - }) + }, +} + +func init() { + AddRule(KnownRootTypeRule.Name, KnownRootTypeRule.RuleFunc) } diff --git a/validator/rules/known_type_names.go b/validator/rules/known_type_names.go index 902939d3..717019fb 100644 --- a/validator/rules/known_type_names.go +++ b/validator/rules/known_type_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownTypeNames", func(observers *Events, addError AddErrFunc) { +var KnownTypeNamesRule = Rule{ + Name: "KnownTypeNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnVariable(func(walker *Walker, variable *ast.VariableDefinition) { typeName := variable.Type.Name() typdef := walker.Schema.Types[typeName] @@ -57,5 +58,9 @@ func init() { At(fragment.Position), ) }) - }) + }, +} + +func init() { + AddRule(KnownTypeNamesRule.Name, KnownTypeNamesRule.RuleFunc) } diff --git a/validator/rules/lone_anonymous_operation.go b/validator/rules/lone_anonymous_operation.go index fe8bb203..ba71f141 100644 --- a/validator/rules/lone_anonymous_operation.go +++ b/validator/rules/lone_anonymous_operation.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("LoneAnonymousOperation", func(observers *Events, addError AddErrFunc) { +var LoneAnonymousOperationRule = Rule{ + Name: "LoneAnonymousOperation", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { if operation.Name == "" && len(walker.Document.Operations) > 1 { addError( @@ -17,5 +18,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(LoneAnonymousOperationRule.Name, LoneAnonymousOperationRule.RuleFunc) } diff --git a/validator/rules/no_fragment_cycles.go b/validator/rules/no_fragment_cycles.go index a953174f..c80def7c 100644 --- a/validator/rules/no_fragment_cycles.go +++ b/validator/rules/no_fragment_cycles.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "fmt" @@ -10,8 +10,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("NoFragmentCycles", func(observers *Events, addError AddErrFunc) { +var NoFragmentCyclesRule = Rule{ + Name: "NoFragmentCycles", + RuleFunc: func(observers *Events, addError AddErrFunc) { visitedFrags := make(map[string]bool) observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) { @@ -67,7 +68,11 @@ func init() { recursive(fragment) }) - }) + }, +} + +func init() { + AddRule(NoFragmentCyclesRule.Name, NoFragmentCyclesRule.RuleFunc) } func getFragmentSpreads(node ast.SelectionSet) []*ast.FragmentSpread { diff --git a/validator/rules/no_undefined_variables.go b/validator/rules/no_undefined_variables.go index 46c18d12..84ed5fa7 100644 --- a/validator/rules/no_undefined_variables.go +++ b/validator/rules/no_undefined_variables.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("NoUndefinedVariables", func(observers *Events, addError AddErrFunc) { +var NoUndefinedVariablesRule = Rule{ + Name: "NoUndefinedVariables", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnValue(func(walker *Walker, value *ast.Value) { if walker.CurrentOperation == nil || value.Kind != ast.Variable || value.VariableDefinition != nil { return @@ -26,5 +27,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(NoUndefinedVariablesRule.Name, NoUndefinedVariablesRule.RuleFunc) } diff --git a/validator/rules/no_unused_fragments.go b/validator/rules/no_unused_fragments.go index 59d9c15c..e95bbe69 100644 --- a/validator/rules/no_unused_fragments.go +++ b/validator/rules/no_unused_fragments.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("NoUnusedFragments", func(observers *Events, addError AddErrFunc) { +var NoUnusedFragmentsRule = Rule{ + Name: "NoUnusedFragments", + RuleFunc: func(observers *Events, addError AddErrFunc) { inFragmentDefinition := false fragmentNameUsed := make(map[string]bool) @@ -27,5 +28,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(NoUnusedFragmentsRule.Name, NoUnusedFragmentsRule.RuleFunc) } diff --git a/validator/rules/no_unused_variables.go b/validator/rules/no_unused_variables.go index d3088109..425df205 100644 --- a/validator/rules/no_unused_variables.go +++ b/validator/rules/no_unused_variables.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("NoUnusedVariables", func(observers *Events, addError AddErrFunc) { +var NoUnusedVariablesRule = Rule{ + Name: "NoUnusedVariables", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { for _, varDef := range operation.VariableDefinitions { if varDef.Used { @@ -28,5 +29,9 @@ func init() { } } }) - }) + }, +} + +func init() { + AddRule(NoUnusedVariablesRule.Name, NoUnusedVariablesRule.RuleFunc) } diff --git a/validator/rules/overlapping_fields_can_be_merged.go b/validator/rules/overlapping_fields_can_be_merged.go index eaa2035e..d2c66aac 100644 --- a/validator/rules/overlapping_fields_can_be_merged.go +++ b/validator/rules/overlapping_fields_can_be_merged.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "bytes" @@ -11,8 +11,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("OverlappingFieldsCanBeMerged", func(observers *Events, addError AddErrFunc) { +var OverlappingFieldsCanBeMergedRule = Rule{ + Name: "OverlappingFieldsCanBeMerged", + RuleFunc: func(observers *Events, addError AddErrFunc) { /** * Algorithm: * @@ -41,7 +42,7 @@ func init() { * * D) When comparing "between" a set of fields and a referenced fragment, first * a comparison is made between each field in the original set of fields and - * each field in the the referenced set of fields. + * each field in the referenced set of fields. * * E) Also, if any fragment is referenced in the referenced selection set, * then a comparison is made "between" the original set of fields and the @@ -104,7 +105,11 @@ func init() { conflict.addFieldsConflictMessage(addError) } }) - }) + }, +} + +func init() { + AddRule(OverlappingFieldsCanBeMergedRule.Name, OverlappingFieldsCanBeMergedRule.RuleFunc) } type pairSet struct { diff --git a/validator/rules/overlapping_fields_can_be_merged_test.go b/validator/rules/overlapping_fields_can_be_merged_test.go index d7bdb0d6..1cbd43af 100644 --- a/validator/rules/overlapping_fields_can_be_merged_test.go +++ b/validator/rules/overlapping_fields_can_be_merged_test.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "testing" diff --git a/validator/rules/possible_fragment_spreads.go b/validator/rules/possible_fragment_spreads.go index 244e5f20..01255e9b 100644 --- a/validator/rules/possible_fragment_spreads.go +++ b/validator/rules/possible_fragment_spreads.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("PossibleFragmentSpreads", func(observers *Events, addError AddErrFunc) { +var PossibleFragmentSpreadsRule = Rule{ + Name: "PossibleFragmentSpreads", + RuleFunc: func(observers *Events, addError AddErrFunc) { validate := func(walker *Walker, parentDef *ast.Definition, fragmentName string, emitError func()) { if parentDef == nil { return @@ -65,5 +66,9 @@ func init() { ) }) }) - }) + }, +} + +func init() { + AddRule(PossibleFragmentSpreadsRule.Name, PossibleFragmentSpreadsRule.RuleFunc) } diff --git a/validator/rules/provided_required_arguments.go b/validator/rules/provided_required_arguments.go index ab79163b..37428d44 100644 --- a/validator/rules/provided_required_arguments.go +++ b/validator/rules/provided_required_arguments.go @@ -1,14 +1,14 @@ -package validator +package rules import ( - "github.com/vektah/gqlparser/v2/ast" - //nolint:revive // Validator rules each use dot imports for convenience. + "github.com/vektah/gqlparser/v2/ast" . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("ProvidedRequiredArguments", func(observers *Events, addError AddErrFunc) { +var ProvidedRequiredArgumentsRule = Rule{ + Name: "ProvidedRequiredArguments", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnField(func(walker *Walker, field *ast.Field) { if field.Definition == nil { return @@ -60,5 +60,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(ProvidedRequiredArgumentsRule.Name, ProvidedRequiredArgumentsRule.RuleFunc) } diff --git a/validator/rules/scalar_leafs.go b/validator/rules/scalar_leafs.go index 605ab9e8..5628ce2c 100644 --- a/validator/rules/scalar_leafs.go +++ b/validator/rules/scalar_leafs.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("ScalarLeafs", func(observers *Events, addError AddErrFunc) { +var ScalarLeafsRule = Rule{ + Name: "ScalarLeafs", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnField(func(walker *Walker, field *ast.Field) { if field.Definition == nil { return @@ -34,5 +35,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(ScalarLeafsRule.Name, ScalarLeafsRule.RuleFunc) } diff --git a/validator/rules/single_field_subscriptions.go b/validator/rules/single_field_subscriptions.go index 7d4c6843..94a4b304 100644 --- a/validator/rules/single_field_subscriptions.go +++ b/validator/rules/single_field_subscriptions.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "strconv" @@ -10,8 +10,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("SingleFieldSubscriptions", func(observers *Events, addError AddErrFunc) { +var SingleFieldSubscriptionsRule = Rule{ + Name: "SingleFieldSubscriptions", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { if walker.Schema.Subscription == nil || operation.Operation != ast.Subscription { return @@ -40,7 +41,11 @@ func init() { } } }) - }) + }, +} + +func init() { + AddRule(SingleFieldSubscriptionsRule.Name, SingleFieldSubscriptionsRule.RuleFunc) } type topField struct { diff --git a/validator/rules/unique_argument_names.go b/validator/rules/unique_argument_names.go index e977d638..d0c52909 100644 --- a/validator/rules/unique_argument_names.go +++ b/validator/rules/unique_argument_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueArgumentNames", func(observers *Events, addError AddErrFunc) { +var UniqueArgumentNamesRule = Rule{ + Name: "UniqueArgumentNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnField(func(walker *Walker, field *ast.Field) { checkUniqueArgs(field.Arguments, addError) }) @@ -16,7 +17,11 @@ func init() { observers.OnDirective(func(walker *Walker, directive *ast.Directive) { checkUniqueArgs(directive.Arguments, addError) }) - }) + }, +} + +func init() { + AddRule(UniqueArgumentNamesRule.Name, UniqueArgumentNamesRule.RuleFunc) } func checkUniqueArgs(args ast.ArgumentList, addError AddErrFunc) { diff --git a/validator/rules/unique_directives_per_location.go b/validator/rules/unique_directives_per_location.go index 47971ee1..4cab38bd 100644 --- a/validator/rules/unique_directives_per_location.go +++ b/validator/rules/unique_directives_per_location.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueDirectivesPerLocation", func(observers *Events, addError AddErrFunc) { +var UniqueDirectivesPerLocationRule = Rule{ + Name: "UniqueDirectivesPerLocation", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnDirectiveList(func(walker *Walker, directives []*ast.Directive) { seen := map[string]bool{} @@ -22,5 +23,9 @@ func init() { seen[dir.Name] = true } }) - }) + }, +} + +func init() { + AddRule(UniqueDirectivesPerLocationRule.Name, UniqueDirectivesPerLocationRule.RuleFunc) } diff --git a/validator/rules/unique_fragment_names.go b/validator/rules/unique_fragment_names.go index 2c44a437..3d2c7289 100644 --- a/validator/rules/unique_fragment_names.go +++ b/validator/rules/unique_fragment_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueFragmentNames", func(observers *Events, addError AddErrFunc) { +var UniqueFragmentNamesRule = Rule{ + Name: "UniqueFragmentNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { seenFragments := map[string]bool{} observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) { @@ -20,5 +21,9 @@ func init() { } seenFragments[fragment.Name] = true }) - }) + }, +} + +func init() { + AddRule(UniqueFragmentNamesRule.Name, UniqueFragmentNamesRule.RuleFunc) } diff --git a/validator/rules/unique_input_field_names.go b/validator/rules/unique_input_field_names.go index c5fce8ff..3ccbbfe0 100644 --- a/validator/rules/unique_input_field_names.go +++ b/validator/rules/unique_input_field_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueInputFieldNames", func(observers *Events, addError AddErrFunc) { +var UniqueInputFieldNamesRule = Rule{ + Name: "UniqueInputFieldNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnValue(func(walker *Walker, value *ast.Value) { if value.Kind != ast.ObjectValue { return @@ -25,5 +26,9 @@ func init() { seen[field.Name] = true } }) - }) + }, +} + +func init() { + AddRule(UniqueInputFieldNamesRule.Name, UniqueInputFieldNamesRule.RuleFunc) } diff --git a/validator/rules/unique_operation_names.go b/validator/rules/unique_operation_names.go index 49ffbe47..401b0ad3 100644 --- a/validator/rules/unique_operation_names.go +++ b/validator/rules/unique_operation_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueOperationNames", func(observers *Events, addError AddErrFunc) { +var UniqueOperationNamesRule = Rule{ + Name: "UniqueOperationNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { seen := map[string]bool{} observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { @@ -20,5 +21,9 @@ func init() { } seen[operation.Name] = true }) - }) + }, +} + +func init() { + AddRule(UniqueOperationNamesRule.Name, UniqueOperationNamesRule.RuleFunc) } diff --git a/validator/rules/unique_variable_names.go b/validator/rules/unique_variable_names.go index c93948c1..f0e4a200 100644 --- a/validator/rules/unique_variable_names.go +++ b/validator/rules/unique_variable_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueVariableNames", func(observers *Events, addError AddErrFunc) { +var UniqueVariableNamesRule = Rule{ + Name: "UniqueVariableNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { seen := map[string]int{} for _, def := range operation.VariableDefinitions { @@ -22,5 +23,9 @@ func init() { seen[def.Variable]++ } }) - }) + }, +} + +func init() { + AddRule(UniqueVariableNamesRule.Name, UniqueVariableNamesRule.RuleFunc) } diff --git a/validator/rules/values_of_correct_type.go b/validator/rules/values_of_correct_type.go index 914e428e..7784fe0c 100644 --- a/validator/rules/values_of_correct_type.go +++ b/validator/rules/values_of_correct_type.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "errors" @@ -11,8 +11,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("ValuesOfCorrectType", func(observers *Events, addError AddErrFunc) { +var ValuesOfCorrectTypeRule = Rule{ + Name: "ValuesOfCorrectType", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnValue(func(walker *Walker, value *ast.Value) { if value.Definition == nil || value.ExpectedType == nil { return @@ -134,7 +135,11 @@ func init() { panic(fmt.Errorf("unhandled %T", value)) } }) - }) + }, +} + +func init() { + AddRule(ValuesOfCorrectTypeRule.Name, ValuesOfCorrectTypeRule.RuleFunc) } func unexpectedTypeMessage(addError AddErrFunc, v *ast.Value) { diff --git a/validator/rules/variables_are_input_types.go b/validator/rules/variables_are_input_types.go index d16ee021..59b2e6f7 100644 --- a/validator/rules/variables_are_input_types.go +++ b/validator/rules/variables_are_input_types.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("VariablesAreInputTypes", func(observers *Events, addError AddErrFunc) { +var VariablesAreInputTypesRule = Rule{ + Name: "VariablesAreInputTypes", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { for _, def := range operation.VariableDefinitions { if def.Definition == nil { @@ -26,5 +27,9 @@ func init() { } } }) - }) + }, +} + +func init() { + AddRule(VariablesAreInputTypesRule.Name, VariablesAreInputTypesRule.RuleFunc) } diff --git a/validator/rules/variables_in_allowed_position.go b/validator/rules/variables_in_allowed_position.go index e3fd6fbb..75b0f7f7 100644 --- a/validator/rules/variables_in_allowed_position.go +++ b/validator/rules/variables_in_allowed_position.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("VariablesInAllowedPosition", func(observers *Events, addError AddErrFunc) { +var VariablesInAllowedPositionRule = Rule{ + Name: "VariablesInAllowedPosition", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnValue(func(walker *Walker, value *ast.Value) { if value.Kind != ast.Variable || value.ExpectedType == nil || value.VariableDefinition == nil || walker.CurrentOperation == nil { return @@ -36,5 +37,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(VariablesInAllowedPositionRule.Name, VariablesInAllowedPositionRule.RuleFunc) } diff --git a/validator/validator.go b/validator/validator.go index b4f37ce2..36564b23 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -8,22 +8,26 @@ import ( type AddErrFunc func(options ...ErrorOption) -type ruleFunc func(observers *Events, addError AddErrFunc) +type RuleFunc func(observers *Events, addError AddErrFunc) -type rule struct { - name string - rule ruleFunc +type Rule struct { + Name string + RuleFunc RuleFunc } -var rules []rule +var specifiedRules []Rule -// addRule to rule set. +// AddRule adds rule to the rule set. // f is called once each time `Validate` is executed. -func AddRule(name string, f ruleFunc) { - rules = append(rules, rule{name: name, rule: f}) +func AddRule(name string, ruleFunc RuleFunc) { + specifiedRules = append(specifiedRules, Rule{Name: name, RuleFunc: ruleFunc}) } -func Validate(schema *Schema, doc *QueryDocument) gqlerror.List { +func Validate(schema *Schema, doc *QueryDocument, rules ...Rule) gqlerror.List { + if rules == nil { + rules = specifiedRules + } + var errs gqlerror.List if schema == nil { errs = append(errs, gqlerror.Errorf("cannot validate as Schema is nil")) @@ -37,9 +41,9 @@ func Validate(schema *Schema, doc *QueryDocument) gqlerror.List { observers := &Events{} for i := range rules { rule := rules[i] - rule.rule(observers, func(options ...ErrorOption) { + rule.RuleFunc(observers, func(options ...ErrorOption) { err := &gqlerror.Error{ - Rule: rule.name, + Rule: rule.Name, } for _, o := range options { o(err) diff --git a/validator/validator_test.go b/validator/validator_test.go index 50b6ebc2..4fd8442e 100644 --- a/validator/validator_test.go +++ b/validator/validator_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/parser" @@ -138,3 +139,43 @@ func TestNoUnusedVariables(t *testing.T) { require.Nil(t, validator.Validate(s, q)) }) } + +func TestCustomRuleSet(t *testing.T) { + + someRule := validator.Rule{ + Name: "SomeRule", + RuleFunc: func(observers *validator.Events, addError validator.AddErrFunc) { + addError(validator.Message("some error message")) + }, + } + + someOtherRule := validator.Rule{ + Name: "SomeOtherRule", + RuleFunc: func(observers *validator.Events, addError validator.AddErrFunc) { + addError(validator.Message("some other error message")) + }, + } + + s := gqlparser.MustLoadSchema( + &ast.Source{ + Name: "graph/schema.graphqls", + Input: ` + type Query { + bar: String! + } + `, BuiltIn: false}, + ) + + q, err := parser.ParseQuery(&ast.Source{ + Name: "SomeQuery", + Input: ` + query Foo($flag: Boolean!) { + ...Bar + } + `}) + require.NoError(t, err) + errList := validator.Validate(s, q, []validator.Rule{someRule, someOtherRule}...) + require.Len(t, errList, 2) + require.Equal(t, "some error message", errList[0].Message) + require.Equal(t, "some other error message", errList[1].Message) +}