forked from Khan/genqlient
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvalidation.go
112 lines (104 loc) · 3.63 KB
/
validation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package generate
// This file contains helpers to do various bits of validation in the process
// of converting types to Go, notably, for cases where we need to check that
// two types match.
import (
"fmt"
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/parser"
)
// selectionsMatch recursively compares the two selection-sets, and returns an
// error if they differ.
//
// It does not check arguments and directives, only field names, aliases,
// order, and fragment-structure. It does not recurse into named fragments, it
// only checks that their names match.
//
// If both selection-sets are nil/empty, they compare equal.
func selectionsMatch(
pos *ast.Position,
expectedSelectionSet, actualSelectionSet ast.SelectionSet,
) error {
if len(expectedSelectionSet) != len(actualSelectionSet) {
return errorf(
pos, "expected %d fields, got %d",
len(expectedSelectionSet), len(actualSelectionSet))
}
for i, expected := range expectedSelectionSet {
switch expected := expected.(type) {
case *ast.Field:
actual, ok := actualSelectionSet[i].(*ast.Field)
switch {
case !ok:
return errorf(actual.Position,
"expected selection #%d to be field, got %T",
i, actualSelectionSet[i])
case actual.Name != expected.Name:
return errorf(actual.Position,
"expected field %d to be %s, got %s",
i, expected.Name, actual.Name)
case actual.Alias != expected.Alias:
return errorf(actual.Position,
"expected field %d's alias to be %s, got %s",
i, expected.Alias, actual.Alias)
}
err := selectionsMatch(actual.Position, expected.SelectionSet, actual.SelectionSet)
if err != nil {
return fmt.Errorf("in %s sub-selection: %w", actual.Alias, err)
}
case *ast.InlineFragment:
actual, ok := actualSelectionSet[i].(*ast.InlineFragment)
switch {
case !ok:
return errorf(actual.Position,
"expected selection %d to be inline fragment, got %T",
i, actualSelectionSet[i])
case actual.TypeCondition != expected.TypeCondition:
return errorf(actual.Position,
"expected fragment %d to be on type %s, got %s",
i, expected.TypeCondition, actual.TypeCondition)
}
err := selectionsMatch(actual.Position, expected.SelectionSet, actual.SelectionSet)
if err != nil {
return fmt.Errorf("in inline fragment on %s: %w", actual.TypeCondition, err)
}
case *ast.FragmentSpread:
actual, ok := actualSelectionSet[i].(*ast.FragmentSpread)
switch {
case !ok:
return errorf(actual.Position,
"expected selection %d to be fragment spread, got %T",
i, actualSelectionSet[i])
case actual.Name != expected.Name:
return errorf(actual.Position,
"expected fragment %d to be ...%s, got ...%s",
i, expected.Name, actual.Name)
}
}
}
return nil
}
// validateBindingSelection checks that if you requested in your type-binding
// that this type must always request certain fields, then in fact it does.
func (g *generator) validateBindingSelection(
typeName string,
binding *TypeBinding,
pos *ast.Position,
selectionSet ast.SelectionSet,
) error {
if binding.ExpectExactFields == "" {
return nil // no validation requested
}
// HACK: we parse the selection as if it were a query, which is basically
// the same (for syntax purposes; it of course wouldn't validate)
doc, gqlErr := parser.ParseQuery(&ast.Source{Input: binding.ExpectExactFields})
if gqlErr != nil {
return errorf(
nil, "invalid type-binding %s.expect_exact_fields: %w", typeName, gqlErr)
}
err := selectionsMatch(pos, doc.Operations[0].SelectionSet, selectionSet)
if err != nil {
return fmt.Errorf("invalid selection for type-binding %s: %w", typeName, err)
}
return nil
}