diff --git a/internal/schema/schema.go b/internal/schema/schema.go index da37fe1f..be92f181 100644 --- a/internal/schema/schema.go +++ b/internal/schema/schema.go @@ -289,6 +289,7 @@ func resolveField(s *types.Schema, f *types.FieldDefinition) error { } func resolveDirectives(s *types.Schema, directives types.DirectiveList, loc string) error { + alreadySeenNonRepeatable := make(map[string]struct{}) for _, d := range directives { dirName := d.Name.Name dd, ok := s.Directives[dirName] @@ -315,6 +316,14 @@ func resolveDirectives(s *types.Schema, directives types.DirectiveList, loc stri d.Arguments = append(d.Arguments, &types.Argument{Name: arg.Name, Value: arg.Default}) } } + + if dd.Repeatable { + continue + } + if _, seen := alreadySeenNonRepeatable[dirName]; seen { + return errors.Errorf(`non repeatable directive %q can not be repeated. Consider adding "repeatable".`, dirName) + } + alreadySeenNonRepeatable[dirName] = struct{}{} } return nil } diff --git a/internal/schema/schema_test.go b/internal/schema/schema_test.go index 726546b1..f57a33e7 100644 --- a/internal/schema/schema_test.go +++ b/internal/schema/schema_test.go @@ -920,6 +920,22 @@ Second line of the description. return nil }, }, + { + name: "Disallow repeat of a directive if it is not `repeatable`", + sdl: ` + directive @nonrepeatabledirective on FIELD_DEFINITION + type Foo { + bar: String @nonrepeatabledirective @nonrepeatabledirective + } + `, + validateError: func(err error) error { + prefix := `graphql: non repeatable directive "nonrepeatabledirective" can not be repeated. Consider adding "repeatable"` + if err == nil || !strings.HasPrefix(err.Error(), prefix) { + return fmt.Errorf("expected error starting with %q, but got %q", prefix, err) + } + return nil + }, + }, } { t.Run(test.name, func(t *testing.T) { s, err := schema.ParseSchema(test.sdl, test.useStringDescriptions)