Skip to content

Commit 22fa045

Browse files
authored
Refactoring: not to use global rule sets (part 2) (#380)
* Refactor validator to resolve import cycles and add rule-based validation * Implement LoadQueryWithRules, MustLoadQueryWithRules * run go fmt and fix golangcilint * fix test case * fix golangcilint * suppress lints
1 parent 8fe841e commit 22fa045

5 files changed

Lines changed: 670 additions & 3 deletions

File tree

gqlparser.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import (
55
"github.com/vektah/gqlparser/v2/gqlerror"
66
"github.com/vektah/gqlparser/v2/parser"
77
"github.com/vektah/gqlparser/v2/validator"
8-
9-
// Blank import is used to load up the validator rules.
10-
_ "github.com/vektah/gqlparser/v2/validator/rules"
8+
"github.com/vektah/gqlparser/v2/validator/rules"
119
)
1210

1311
func LoadSchema(str ...*ast.Source) (*ast.Schema, error) {
@@ -30,6 +28,7 @@ func MustLoadSchema(str ...*ast.Source) *ast.Schema {
3028
return s
3129
}
3230

31+
// Deprecated: use LoadQueryWithRules instead.
3332
func LoadQuery(schema *ast.Schema, str string) (*ast.QueryDocument, gqlerror.List) {
3433
query, err := parser.ParseQuery(&ast.Source{Input: str})
3534
if err != nil {
@@ -47,10 +46,36 @@ func LoadQuery(schema *ast.Schema, str string) (*ast.QueryDocument, gqlerror.Lis
4746
return query, nil
4847
}
4948

49+
func LoadQueryWithRules(schema *ast.Schema, str string, rules *rules.Rules) (*ast.QueryDocument, gqlerror.List) {
50+
query, err := parser.ParseQuery(&ast.Source{Input: str})
51+
if err != nil {
52+
gqlErr, ok := err.(*gqlerror.Error)
53+
if ok {
54+
return nil, gqlerror.List{gqlErr}
55+
}
56+
return nil, gqlerror.List{gqlerror.Wrap(err)}
57+
}
58+
errs := validator.ValidateWithRules(schema, query, rules)
59+
if len(errs) > 0 {
60+
return nil, errs
61+
}
62+
63+
return query, nil
64+
}
65+
66+
// Deprecated: use MustLoadQueryWithRules instead.
5067
func MustLoadQuery(schema *ast.Schema, str string) *ast.QueryDocument {
5168
q, err := LoadQuery(schema, str)
5269
if err != nil {
5370
panic(err)
5471
}
5572
return q
5673
}
74+
75+
func MustLoadQueryWithRules(schema *ast.Schema, str string, rules *rules.Rules) *ast.QueryDocument {
76+
q, err := LoadQueryWithRules(schema, str, rules)
77+
if err != nil {
78+
panic(err)
79+
}
80+
return q
81+
}

validator/imported_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func TestValidation(t *testing.T) {
5959
}
6060

6161
runSpec(t, schemas, deviations, path)
62+
runSpecWithRules(t, schemas, deviations, path)
6263
return nil
6364
})
6465
require.NoError(t, err)
@@ -97,6 +98,7 @@ func runSpec(t *testing.T, schemas []*ast.Schema, deviations []*Deviation, filen
9798
} else {
9899
schema = schemas[idx]
99100
}
101+
//nolint:staticcheck
100102
_, errList := gqlparser.LoadQuery(schema, spec.Query)
101103
var finalErrors gqlerror.List
102104
for _, err := range errList {
@@ -161,6 +163,103 @@ func runSpec(t *testing.T, schemas []*ast.Schema, deviations []*Deviation, filen
161163
})
162164
}
163165

166+
func runSpecWithRules(t *testing.T, schemas []*ast.Schema, deviations []*Deviation, filename string) {
167+
ruleName := strings.TrimSuffix(filepath.Base(filename), ".spec.yml")
168+
169+
var specs []Spec
170+
readYaml(filename, &specs)
171+
t.Run(ruleName, func(t *testing.T) {
172+
for _, spec := range specs {
173+
if len(spec.Errors) == 0 {
174+
spec.Errors = nil
175+
}
176+
t.Run(spec.Name, func(t *testing.T) {
177+
for _, deviation := range deviations {
178+
if deviation.pattern.MatchString(ruleName + "/" + spec.Name) {
179+
if deviation.Skip != "" {
180+
t.Skip(deviation.Skip)
181+
}
182+
if deviation.Errors != nil {
183+
spec.Errors = deviation.Errors
184+
}
185+
}
186+
}
187+
188+
// idx := spec.Schema
189+
var schema *ast.Schema
190+
if idx, err := strconv.Atoi(spec.Schema); err != nil {
191+
var gqlErr error
192+
schema, gqlErr = gqlparser.LoadSchema(&ast.Source{Input: spec.Schema, Name: spec.Name})
193+
if gqlErr != nil {
194+
t.Fatal(err)
195+
}
196+
} else {
197+
schema = schemas[idx]
198+
}
199+
_, errList := gqlparser.LoadQueryWithRules(schema, spec.Query, nil)
200+
var finalErrors gqlerror.List
201+
for _, err := range errList {
202+
// ignore errors from other rules
203+
if spec.Rule != "" && err.Rule != spec.Rule {
204+
continue
205+
}
206+
finalErrors = append(finalErrors, err)
207+
}
208+
209+
for i := range spec.Errors {
210+
spec.Errors[i].Rule = spec.Rule
211+
212+
// remove inconsistent use of ;
213+
spec.Errors[i].Message = strings.ReplaceAll(spec.Errors[i].Message, "; Did you mean", ". Did you mean")
214+
}
215+
sort.Slice(spec.Errors, compareErrors(spec.Errors))
216+
sort.Slice(finalErrors, compareErrors(finalErrors))
217+
218+
if len(finalErrors) != len(spec.Errors) {
219+
t.Errorf("wrong number of errors returned\ngot:\n%s\nwant:\n%s", finalErrors.Error(), spec.Errors)
220+
} else {
221+
for i := range spec.Errors {
222+
expected := spec.Errors[i]
223+
actual := finalErrors[i]
224+
if actual.Rule != spec.Rule {
225+
continue
226+
}
227+
var errLocs []string
228+
if expected.Message != actual.Message {
229+
errLocs = append(errLocs, "message mismatch")
230+
}
231+
if len(expected.Locations) > 0 && len(actual.Locations) == 0 {
232+
errLocs = append(errLocs, "missing location")
233+
}
234+
if len(expected.Locations) > 0 && len(actual.Locations) > 0 {
235+
found := false
236+
for _, loc := range expected.Locations {
237+
if actual.Locations[0].Line == loc.Line {
238+
found = true
239+
break
240+
}
241+
}
242+
243+
if !found {
244+
errLocs = append(errLocs, "line")
245+
}
246+
}
247+
248+
if len(errLocs) > 0 {
249+
t.Errorf("%s\ngot: %s\nwant: %s", strings.Join(errLocs, ", "), finalErrors[i].Error(), spec.Errors[i].Error())
250+
}
251+
}
252+
}
253+
254+
if t.Failed() {
255+
t.Logf("name: '%s'", spec.Name)
256+
t.Log("\nquery:", spec.Query)
257+
}
258+
})
259+
}
260+
})
261+
}
262+
164263
func compareErrors(errors gqlerror.List) func(i, j int) bool {
165264
return func(i, j int) bool {
166265
cmp := strings.Compare(errors[i].Message, errors[j].Message)

validator/validator.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func ReplaceRule(name string, ruleFunc RuleFunc) {
7676
specifiedRules = result
7777
}
7878

79+
// Deprecated: use ValidateWithRules instead.
7980
func Validate(schema *Schema, doc *QueryDocument, rules ...Rule) gqlerror.List {
8081
if rules == nil {
8182
rules = specifiedRules
@@ -108,3 +109,35 @@ func Validate(schema *Schema, doc *QueryDocument, rules ...Rule) gqlerror.List {
108109
Walk(schema, doc, observers)
109110
return errs
110111
}
112+
113+
func ValidateWithRules(schema *Schema, doc *QueryDocument, rules *validatorrules.Rules) gqlerror.List {
114+
if rules == nil {
115+
rules = validatorrules.NewDefaultRules()
116+
}
117+
118+
var errs gqlerror.List
119+
if schema == nil {
120+
errs = append(errs, gqlerror.Errorf("cannot validate as Schema is nil"))
121+
}
122+
if doc == nil {
123+
errs = append(errs, gqlerror.Errorf("cannot validate as QueryDocument is nil"))
124+
}
125+
if len(errs) > 0 {
126+
return errs
127+
}
128+
observers := &core.Events{}
129+
for name, ruleFunc := range rules.GetInner() {
130+
ruleFunc(observers, func(options ...ErrorOption) {
131+
err := &gqlerror.Error{
132+
Rule: name,
133+
}
134+
for _, o := range options {
135+
o(err)
136+
}
137+
errs = append(errs, err)
138+
})
139+
}
140+
141+
Walk(schema, doc, observers)
142+
return errs
143+
}

0 commit comments

Comments
 (0)