Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
.DS_Store
pkg/parser/testdata/lotto.graphql
*node_modules*
*vendor*
*vendor*
.vscode
16 changes: 16 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Allow for code examples.
no-hard-tabs:
code_blocks: false

# Long lines are OK.
line-length: false

# We get excited!
no-trailing-punctuation:
punctuation: ".,;:"

# Allow inline HTML.
no-inline-html: false

# We lead with badges, it looks right.
first-line-h1: false
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[![GoDoc](https://pkg.go.dev/badge/github.com/wundergraph/graphql-go-tools/v2)](https://pkg.go.dev/github.com/wundergraph/graphql-go-tools/v2)
[![v2-ci](https://github.com/wundergraph/graphql-go-tools/workflows/v2-ci/badge.svg)](https://github.com/wundergraph/graphql-go-tools/actions/workflows/v2.yml)

# GraphQL Router / API Gateway Framework written in Golang

[<p align="center"><img height="auto" src="./assets/logo.png"></p>](https://wundergraph.com/)
[<p align="center"><img height="auto" src="./assets/logo.png" alt="Wundergraph logo"></p>](https://wundergraph.com/)

## We're hiring!

Expand Down Expand Up @@ -35,6 +36,7 @@ If you're looking for a complete ready-to-use Open Source Router for Federation,
have a look at the [Cosmo Router](https://github.com/wundergraph/cosmo) which is based on this library.

Cosmo Router wraps this library and provides a complete solution for Federated GraphQL including the following features:

- [x] Federation Gateway
- [x] OpenTelemetry Metrics & Distributed Tracing
- [x] Prometheus Metrics
Expand All @@ -60,7 +62,6 @@ This repository contains multiple packages joined via [workspace](https://github
| [examples/federation](https://github.com/wundergraph/graphql-go-tools/blob/master/examples/federation/go.mod) | Example implementation of graphql federation gateway. This example is not production ready. For production ready solution please consider using [cosmo router](https://github.com/wundergraph/cosmo/tree/main) | depends on [execution](https://github.com/wundergraph/graphql-go-tools/blob/master/execution/go.mod) package | actual federation gateway example |
| [graphql-go-tools v1](https://github.com/wundergraph/graphql-go-tools/blob/master/go.mod) | Legacy GraphQL engine implementation. This version 1 package is in maintenance mode and accepts only pull requests with critical bug fixes. All new features will be implemented in the version 2 package only. | - | deprecated, maintenance mode |


## Notes

This library is used in production at [WunderGraph](https://wundergraph.com/).
Expand Down
50 changes: 28 additions & 22 deletions v2/pkg/ast/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,29 +77,16 @@ func (p Path) WithoutInlineFragmentNames() Path {
return out
}

func (p Path) String() string {
out := "["
for i := range p {
if i != 0 {
out += ","
}
switch p[i].Kind {
case ArrayIndex:
out += strconv.Itoa(p[i].ArrayIndex)
case FieldName:
if len(p[i].FieldName) == 0 {
out += "query"
} else {
out += unsafebytes.BytesToString(p[i].FieldName)
}
case InlineFragmentName:
out += InlineFragmentPathPrefix
out += strconv.Itoa(p[i].FragmentRef)
out += unsafebytes.BytesToString(p[i].FieldName)
}
func (p Path) StringSlice() []string {
ret := make([]string, len(p))
for i, item := range p {
ret[i] = item.String()
}
out += "]"
return out
return ret
}

func (p Path) String() string {
return "[" + strings.Join(p.StringSlice(), ",") + "]"
}

func (p Path) DotDelimitedString() string {
Expand Down Expand Up @@ -142,6 +129,25 @@ func (p Path) DotDelimitedString() string {
return builder.String()
}

func (p PathItem) String() string {
switch p.Kind {
case ArrayIndex:
return strconv.Itoa(p.ArrayIndex)
case FieldName:
out := "query"
if len(p.FieldName) != 0 {
out = unsafebytes.BytesToString(p.FieldName)
}
return out
case InlineFragmentName:
out := InlineFragmentPathPrefix
out += strconv.Itoa(p.FragmentRef)
out += unsafebytes.BytesToString(p.FieldName)
return out
}
return ""
}

func (p *PathItem) UnmarshalJSON(data []byte) error {
if data == nil {
return fmt.Errorf("data must not be nil")
Expand Down
16 changes: 16 additions & 0 deletions v2/pkg/asttransform/baseschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ directive @skip(
"Skipped when true."
if: Boolean!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to defer this fragment when the if argument is true or undefined."
directive @defer(
"A unique identifier for the results."
label: String
"Controls whether the fragment will be deferred, usually via a variable."
if: Boolean! = true
) on FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to stream this array field when the if argument is true or undefined."
directive @stream(
"A unique identifier for the results."
label: String
"Controls streaming, usually via a variable."
if: Boolean! = true
"The number of results to include in the initial (non-streamed) response."
initialCount: Int = 0
) on FIELD
"Marks an element of a GraphQL schema as no longer supported."
directive @deprecated(
"""
Expand Down
12 changes: 7 additions & 5 deletions v2/pkg/engine/plan/analyze_plan_kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport"
)

func AnalyzePlanKind(operation, definition *ast.Document, operationName string) (operationType ast.OperationType, streaming bool, error error) {
func AnalyzePlanKind(operation, definition *ast.Document, operationName string) (operationType ast.OperationType, error error) {
walker := astvisitor.NewWalker(48)
visitor := &planKindVisitor{
Walker: &walker,
Expand All @@ -20,10 +20,9 @@ func AnalyzePlanKind(operation, definition *ast.Document, operationName string)
var report operationreport.Report
walker.Walk(operation, definition, &report)
if report.HasErrors() {
return ast.OperationTypeUnknown, false, report
return ast.OperationTypeUnknown, report
}
operationType = visitor.operationType
streaming = visitor.hasDeferDirective || visitor.hasStreamDirective
return
}

Expand All @@ -41,11 +40,14 @@ func (p *planKindVisitor) EnterDirective(ref int) {
switch ancestor.Kind {
case ast.NodeKindField:
switch directiveName {
case "defer":
p.hasDeferDirective = true
case "stream":
p.hasStreamDirective = true
}
case ast.NodeKindInlineFragment:
switch directiveName {
case "defer":
p.hasStreamDirective = true
}
}
}

Expand Down
36 changes: 14 additions & 22 deletions v2/pkg/engine/plan/analyze_plan_kind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import (
"github.com/wundergraph/graphql-go-tools/v2/pkg/asttransform"
)

type expectation func(t *testing.T, operationKind ast.OperationType, streaming bool, err error)
type expectation func(t *testing.T, operationKind ast.OperationType, err error)

func mustNotErr() expectation {
return func(t *testing.T, operationKind ast.OperationType, streaming bool, err error) {
return func(t *testing.T, operationKind ast.OperationType, err error) {
assert.NoError(t, err)
}
}

func mustSubscription(expect bool) expectation {
return func(t *testing.T, operationKind ast.OperationType, streaming bool, err error) {
return func(t *testing.T, operationKind ast.OperationType, err error) {
if expect {
assert.Equal(t, ast.OperationTypeSubscription, operationKind)
} else {
Expand All @@ -29,12 +29,6 @@ func mustSubscription(expect bool) expectation {
}
}

func mustStreaming(expectStreaming bool) expectation {
return func(t *testing.T, operationKind ast.OperationType, streaming bool, err error) {
assert.Equal(t, expectStreaming, streaming)
}
}

func TestAnalyzePlanKind(t *testing.T) {
run := func(definition, operation, operationName string, expectations ...expectation) func(t *testing.T) {
return func(t *testing.T) {
Expand All @@ -44,9 +38,9 @@ func TestAnalyzePlanKind(t *testing.T) {
if err != nil {
t.Fatal(err)
}
operationKind, streaming, err := AnalyzePlanKind(&op, &def, operationName)
operationKind, err := AnalyzePlanKind(&op, &def, operationName)
for i := range expectations {
expectations[i](t, operationKind, streaming, err)
expectations[i](t, operationKind, err)
}
}
}
Expand All @@ -67,7 +61,6 @@ func TestAnalyzePlanKind(t *testing.T) {
}`,
"MyQuery",
mustNotErr(),
mustStreaming(false),
mustSubscription(false),
))
t.Run("query stream", run(testDefinition, `
Expand All @@ -86,7 +79,6 @@ func TestAnalyzePlanKind(t *testing.T) {
}`,
"MyQuery",
mustNotErr(),
mustStreaming(true),
mustSubscription(false),
))
t.Run("query defer", run(testDefinition, `
Expand All @@ -100,12 +92,13 @@ func TestAnalyzePlanKind(t *testing.T) {
name
}
primaryFunction
favoriteEpisode @defer
... @defer {
favoriteEpisode
}
}
}`,
"MyQuery",
mustNotErr(),
mustStreaming(true),
mustSubscription(false),
))
t.Run("query defer", run(testDefinition, `
Expand All @@ -132,7 +125,6 @@ func TestAnalyzePlanKind(t *testing.T) {
}`,
"MyQuery",
mustNotErr(),
mustStreaming(false),
mustSubscription(false),
))
t.Run("query defer different name", run(testDefinition, `
Expand All @@ -146,12 +138,13 @@ func TestAnalyzePlanKind(t *testing.T) {
name
}
primaryFunction
favoriteEpisode @defer
... @defer {
favoriteEpisode
}
}
}`,
"OperationNameNotExists",
mustNotErr(),
mustStreaming(false),
mustSubscription(false),
))
t.Run("subscription", run(testDefinition, `
Expand All @@ -160,19 +153,19 @@ func TestAnalyzePlanKind(t *testing.T) {
}`,
"RemainingJedis",
mustNotErr(),
mustStreaming(false),
mustSubscription(true),
))
t.Run("subscription with streaming", run(testDefinition, `
subscription NewReviews {
newReviews {
id
stars @defer
... @defer {
stars
}
}
}`,
"NewReviews",
mustNotErr(),
mustStreaming(true),
mustSubscription(true),
))
t.Run("subscription name not exists", run(testDefinition, `
Expand All @@ -181,7 +174,6 @@ func TestAnalyzePlanKind(t *testing.T) {
}`,
"OperationNameNotExists",
mustNotErr(),
mustStreaming(false),
mustSubscription(false),
))
}
Loading