Skip to content
This repository was archived by the owner on Apr 1, 2024. It is now read-only.

Commit fff53f6

Browse files
authored
Merge pull request #16 from Code-Hex/fix/use-code-hex-gqlparser
use code-hex gqlparser
2 parents 3aebf8d + da75ecd commit fff53f6

File tree

13 files changed

+853
-176
lines changed

13 files changed

+853
-176
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.16
44

55
require (
66
github.com/99designs/gqlgen v0.13.0
7+
github.com/Code-Hex/gqlparser/v2 v2.1.1-0.20210404043438-758ac252308a
8+
github.com/agnivade/levenshtein v1.1.0 // indirect
79
github.com/google/go-cmp v0.5.5
810
github.com/machinebox/graphql v0.2.2
911
github.com/matryer/is v1.4.0 // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA=
22
github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk=
33
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4+
github.com/Code-Hex/gqlparser/v2 v2.1.1-0.20210404043438-758ac252308a h1:IzMV22Hitzxqb71um5i64jEJTyeUVwemK8JmcQdgpA4=
5+
github.com/Code-Hex/gqlparser/v2 v2.1.1-0.20210404043438-758ac252308a/go.mod h1:0sYgRh/Er/ZhHrcKw516TOX4K0ul94vKcBSXAPhLWDc=
46
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
5-
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
67
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
8+
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
9+
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
710
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
811
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
912
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
@@ -13,8 +16,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
1316
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1417
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1518
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16-
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
1719
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
20+
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
21+
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
1822
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
1923
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
2024
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=

internal/gqlgen/gqlgen.go

Lines changed: 131 additions & 147 deletions
Large diffs are not rendered by default.

internal/graphql/context_operation.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package graphql
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/Code-Hex/gqlparser/v2/ast"
8+
)
9+
10+
type OperationContext struct {
11+
RawQuery string
12+
Variables map[string]interface{}
13+
OperationName string
14+
Doc *ast.QueryDocument
15+
16+
Operation *ast.OperationDefinition
17+
}
18+
19+
func (c *OperationContext) Validate(ctx context.Context) error {
20+
if c.Doc == nil {
21+
return errors.New("field 'Doc'is required")
22+
}
23+
if c.RawQuery == "" {
24+
return errors.New("field 'RawQuery' is required")
25+
}
26+
if c.Variables == nil {
27+
c.Variables = make(map[string]interface{})
28+
}
29+
return nil
30+
}
31+
32+
type operationCtx struct{}
33+
34+
func GetOperationContext(ctx context.Context) *OperationContext {
35+
if val, ok := ctx.Value(operationCtx{}).(*OperationContext); ok && val != nil {
36+
return val
37+
}
38+
panic("missing operation context")
39+
}
40+
41+
func WithOperationContext(ctx context.Context, rc *OperationContext) context.Context {
42+
return context.WithValue(ctx, operationCtx{}, rc)
43+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package graphql
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
)
9+
10+
func TestGetOperationContext(t *testing.T) {
11+
rc := &OperationContext{}
12+
13+
ctx := WithOperationContext(context.Background(), rc)
14+
15+
got := GetOperationContext(ctx)
16+
17+
if diff := cmp.Diff(rc, got); diff != "" {
18+
t.Errorf("(-want, +got)\n%s", diff)
19+
}
20+
}

internal/graphql/executable_schema.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package graphql
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Code-Hex/gqlparser/v2/ast"
7+
)
8+
9+
// CollectFields returns the set of fields from an ast.SelectionSet where all collected fields satisfy at least one of the GraphQL types
10+
// passed through satisfies. Providing an empty or nil slice for satisfies will return collect all fields regardless of fragment
11+
// type conditions.
12+
func CollectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string) []CollectedField {
13+
return collectFields(reqCtx, selSet, satisfies, map[string]bool{})
14+
}
15+
16+
func collectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string, visited map[string]bool) []CollectedField {
17+
groupedFields := make([]CollectedField, 0, len(selSet))
18+
19+
for _, sel := range selSet {
20+
switch sel := sel.(type) {
21+
case *ast.Field:
22+
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
23+
continue
24+
}
25+
f := getOrCreateAndAppendField(&groupedFields, sel.Name, sel.Alias, sel.ObjectDefinition, func() CollectedField {
26+
return CollectedField{Field: sel}
27+
})
28+
29+
f.Selections = append(f.Selections, sel.SelectionSet...)
30+
case *ast.InlineFragment:
31+
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
32+
continue
33+
}
34+
if len(satisfies) > 0 && !instanceOf(sel.TypeCondition, satisfies) {
35+
continue
36+
}
37+
for _, childField := range collectFields(reqCtx, sel.SelectionSet, satisfies, visited) {
38+
f := getOrCreateAndAppendField(&groupedFields, childField.Name, childField.Alias, childField.ObjectDefinition, func() CollectedField { return childField })
39+
f.Selections = append(f.Selections, childField.Selections...)
40+
}
41+
42+
case *ast.FragmentSpread:
43+
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
44+
continue
45+
}
46+
fragmentName := sel.Name
47+
if _, seen := visited[fragmentName]; seen {
48+
continue
49+
}
50+
visited[fragmentName] = true
51+
52+
fragment := reqCtx.Doc.Fragments.ForName(fragmentName)
53+
if fragment == nil {
54+
// should never happen, validator has already run
55+
panic(fmt.Errorf("missing fragment %s", fragmentName))
56+
}
57+
58+
if len(satisfies) > 0 && !instanceOf(fragment.TypeCondition, satisfies) {
59+
continue
60+
}
61+
62+
for _, childField := range collectFields(reqCtx, fragment.SelectionSet, satisfies, visited) {
63+
f := getOrCreateAndAppendField(&groupedFields, childField.Name, childField.Alias, childField.ObjectDefinition, func() CollectedField { return childField })
64+
f.Selections = append(f.Selections, childField.Selections...)
65+
}
66+
default:
67+
panic(fmt.Errorf("unsupported %T", sel))
68+
}
69+
}
70+
71+
return groupedFields
72+
}
73+
74+
type CollectedField struct {
75+
*ast.Field
76+
77+
Selections ast.SelectionSet
78+
}
79+
80+
func instanceOf(val string, satisfies []string) bool {
81+
for _, s := range satisfies {
82+
if val == s {
83+
return true
84+
}
85+
}
86+
return false
87+
}
88+
89+
func getOrCreateAndAppendField(c *[]CollectedField, name string, alias string, objectDefinition *ast.Definition, creator func() CollectedField) *CollectedField {
90+
for i, cf := range *c {
91+
if cf.Name == name && cf.Alias == alias && (cf.ObjectDefinition == objectDefinition || (cf.ObjectDefinition != nil && objectDefinition != nil && cf.ObjectDefinition.Name == objectDefinition.Name)) {
92+
return &(*c)[i]
93+
}
94+
}
95+
96+
f := creator()
97+
98+
*c = append(*c, f)
99+
return &(*c)[len(*c)-1]
100+
}
101+
102+
func shouldIncludeNode(directives ast.DirectiveList, variables map[string]interface{}) bool {
103+
if len(directives) == 0 {
104+
return true
105+
}
106+
107+
skip, include := false, true
108+
109+
if d := directives.ForName("skip"); d != nil {
110+
skip = resolveIfArgument(d, variables)
111+
}
112+
113+
if d := directives.ForName("include"); d != nil {
114+
include = resolveIfArgument(d, variables)
115+
}
116+
117+
return !skip && include
118+
}
119+
120+
func resolveIfArgument(d *ast.Directive, variables map[string]interface{}) bool {
121+
arg := d.Arguments.ForName("if")
122+
if arg == nil {
123+
panic(fmt.Sprintf("%s: argument 'if' not defined", d.Name))
124+
}
125+
value, err := arg.Value.Value(variables)
126+
if err != nil {
127+
panic(err)
128+
}
129+
ret, ok := value.(bool)
130+
if !ok {
131+
panic(fmt.Sprintf("%s: argument 'if' is not a boolean", d.Name))
132+
}
133+
return ret
134+
}

internal/graphql/fieldset.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package graphql
2+
3+
import (
4+
"io"
5+
"sync"
6+
7+
gql "github.com/99designs/gqlgen/graphql"
8+
)
9+
10+
type FieldSet struct {
11+
fields []CollectedField
12+
Values []gql.Marshaler
13+
delayed []delayedResult
14+
}
15+
16+
type delayedResult struct {
17+
i int
18+
f func() gql.Marshaler
19+
}
20+
21+
func NewFieldSet(fields []CollectedField) *FieldSet {
22+
return &FieldSet{
23+
fields: fields,
24+
Values: make([]gql.Marshaler, len(fields)),
25+
}
26+
}
27+
28+
func (m *FieldSet) Concurrently(i int, f func() gql.Marshaler) {
29+
m.delayed = append(m.delayed, delayedResult{i: i, f: f})
30+
}
31+
32+
func (m *FieldSet) Dispatch() {
33+
if len(m.delayed) == 1 {
34+
// only one concurrent task, no need to spawn a goroutine or deal create waitgroups
35+
d := m.delayed[0]
36+
m.Values[d.i] = d.f()
37+
} else if len(m.delayed) > 1 {
38+
// more than one concurrent task, use the main goroutine to do one, only spawn goroutines for the others
39+
40+
var wg sync.WaitGroup
41+
for _, d := range m.delayed[1:] {
42+
wg.Add(1)
43+
go func(d delayedResult) {
44+
m.Values[d.i] = d.f()
45+
wg.Done()
46+
}(d)
47+
}
48+
49+
m.Values[m.delayed[0].i] = m.delayed[0].f()
50+
wg.Wait()
51+
}
52+
}
53+
54+
var openBrace = []byte(`{`)
55+
var closeBrace = []byte(`}`)
56+
var colon = []byte(`:`)
57+
var comma = []byte(`,`)
58+
59+
func (m *FieldSet) MarshalGQL(writer io.Writer) {
60+
writer.Write(openBrace)
61+
for i, field := range m.fields {
62+
if i != 0 {
63+
writer.Write(comma)
64+
}
65+
writeQuotedString(writer, field.Alias)
66+
writer.Write(colon)
67+
m.Values[i].MarshalGQL(writer)
68+
}
69+
writer.Write(closeBrace)
70+
}
71+
72+
const encodeHex = "0123456789ABCDEF"
73+
74+
func writeQuotedString(w io.Writer, s string) {
75+
start := 0
76+
io.WriteString(w, `"`)
77+
78+
for i, c := range s {
79+
if c < 0x20 || c == '\\' || c == '"' {
80+
io.WriteString(w, s[start:i])
81+
82+
switch c {
83+
case '\t':
84+
io.WriteString(w, `\t`)
85+
case '\r':
86+
io.WriteString(w, `\r`)
87+
case '\n':
88+
io.WriteString(w, `\n`)
89+
case '\\':
90+
io.WriteString(w, `\\`)
91+
case '"':
92+
io.WriteString(w, `\"`)
93+
default:
94+
io.WriteString(w, `\u00`)
95+
w.Write([]byte{encodeHex[c>>4], encodeHex[c&0xf]})
96+
}
97+
98+
start = i + 1
99+
}
100+
}
101+
102+
io.WriteString(w, s[start:])
103+
io.WriteString(w, `"`)
104+
}

0 commit comments

Comments
 (0)