Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync-Up With graph-gophers/graphql-go(Fork's) Master #1

Open
wants to merge 97 commits into
base: fix-resp
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
177b9a1
Support subscriptions
matiasanaya Apr 25, 2018
3add01d
Remove stdErrors alias
matiasanaya May 7, 2018
a2b77e9
Fix graphql indentation
matiasanaya May 7, 2018
cb1a8ec
Fix formatJSON() call after upstream change
matiasanaya May 11, 2018
a8035b8
graphql_test: rename type Enum to make room for another tested enum type
technoweenie Jun 13, 2018
69226a0
internal/exec: Support Stringers as GraphQL enum values.
technoweenie Jun 13, 2018
e5aa273
graphql: Add enum output benchmark.
technoweenie Jun 13, 2018
9499556
Properly handle nil interface resolvers
tsholmes Jul 13, 2018
0217557
Also handle typed nil
tsholmes Jul 13, 2018
cde994f
Fill in variables with specified defaults
tsholmes Jul 13, 2018
1d9248e
Handle nil variables map
tsholmes Jul 13, 2018
f2ab787
reverse order of errors vs data in response
gracenoah Sep 21, 2018
9e8b5cc
panic message should me meaningful: invalid type should say which typ…
kirillDanshin Sep 26, 2018
1e3c769
fixed typos throughout project
Sep 28, 2018
18072e1
add test for nil interface resolvers
sqs Sep 29, 2018
45ec3c9
Add test
tsholmes Oct 1, 2018
146e8a0
Merge pull request #265 from C3Develop/feature/meaningful-panic
pavelnikolov Oct 2, 2018
151d104
Merge pull request #268 from kschumy/fix-typos
pavelnikolov Oct 2, 2018
ad74b2b
add a comment
gracenoah Oct 2, 2018
25d6d94
Merge pull request #264 from gracenoah/order-errors
pavelnikolov Oct 2, 2018
634acfd
parse string based descriptions
gracenoah Oct 3, 2018
f63fb79
add an option to handle comments according to the new spec
gracenoah Oct 3, 2018
98cfed7
fix bug with eager string consumption
gracenoah Oct 3, 2018
d79e178
fix mixed comment / description handling
gracenoah Oct 3, 2018
71460bc
graphqlerrors -> gqlerrors
tsholmes Oct 7, 2018
a2b6fc3
Merge pull request #239 from wandb/fix/nil_interface
pavelnikolov Oct 8, 2018
b1ba9ff
rename NoCommentsAsDesciptions to UseStringDescriptions
gracenoah Oct 15, 2018
293a8b4
Merge branch 'master' into fix-enum-resolution
technoweenie Oct 16, 2018
f366350
cleanup benchmark for 'go vet'
technoweenie Oct 16, 2018
5a22de2
remove enum benchmark
technoweenie Oct 16, 2018
673528f
Merge pull request #218 from technoweenie/fix-enum-resolution
pavelnikolov Oct 16, 2018
62c5401
Add extensions in QueryError
ivanp Oct 16, 2018
ffc7e8e
Merge pull request #238 from wandb/fix/defaults
pavelnikolov Oct 16, 2018
ecd5bd1
Support for custom interface that has Extensions() method
ivanp Oct 17, 2018
502f0bf
Merge pull request #272 from kumparan/errors-extensions
pavelnikolov Oct 17, 2018
c3e1fe5
simpler error comparisons
alicebob Sep 27, 2018
b174f9e
Merge pull request #203 from matiasanaya/feature/subscriptions
pavelnikolov Oct 22, 2018
2b2d3e5
Add subscriptions feature to the README.md
Oct 22, 2018
79bede9
Merge pull request #278 from pavelnikolov/improve-docs
pavelnikolov Oct 22, 2018
6c6e29f
remove impossible cases, improve tests
gracenoah Oct 22, 2018
940d2b0
Merge pull request #269 from gracenoah/descriptions
pavelnikolov Oct 22, 2018
86130ac
Use struct fields as resolvers instead of methods (#28)
0xSalman Oct 30, 2018
074fe87
Fix #286: Support queries/mutations through alternative transports
matiasanaya Nov 10, 2018
9b57fd8
Merge pull request #277 from alicebob/simpleerr
pavelnikolov Nov 12, 2018
995849d
Merge pull request #287 from matiasanaya/feature/support-query-throug…
pavelnikolov Nov 12, 2018
6fa3ec9
Switch to interface{} for Subscribe() return
matiasanaya Nov 13, 2018
050454c
Update testing.go
fadi-alkatut Nov 13, 2018
fd99376
Merge pull request #291 from fadi-alkatut/master
pavelnikolov Nov 16, 2018
0079757
Merge pull request #289 from matiasanaya/feature/subs-adapter
pavelnikolov Nov 28, 2018
9be23d9
add SubPathHasError util on Request
gracenoah Jun 20, 2018
e70b076
respect null/non-null fields
gracenoah Jun 20, 2018
1f86e59
add error propagation tests
gracenoah Jun 19, 2018
1626225
handle subscription errors
gracenoah Oct 23, 2018
84670bd
fix handling of nullables in subscriptions
gracenoah Oct 23, 2018
af5c8a8
test more edge cases of error propagation
gracenoah Oct 23, 2018
192cb83
add a test for slices
gracenoah Oct 23, 2018
c948eab
propagate errors when non-nullable fields resolve to null
tinnywang Dec 12, 2018
7e5a06c
sort errors for deterministic test results
tinnywang Dec 12, 2018
0009cb0
more tests for error propagation in lists
tinnywang Dec 13, 2018
6d09675
propagate errors in lists
tinnywang Dec 13, 2018
5cfcb76
more tests
tinnywang Dec 13, 2018
12c3e95
check nullability of immediate children
tinnywang Dec 14, 2018
08598f5
cleanup
tinnywang Dec 18, 2018
07f2eb0
convert to go module
joefitzgerald Dec 31, 2018
0f3ab7c
Update schema.go
fadi-alkatut Jan 8, 2019
403121a
test interface validation
fadi-alkatut Jan 8, 2019
f51ee68
Update schema_internal_test.go
fadi-alkatut Jan 8, 2019
81cbbab
Update schema_internal_test.go
fadi-alkatut Jan 8, 2019
25dda10
Update schema_test.go
fadi-alkatut Jan 8, 2019
27cde2c
Update schema_test.go
fadi-alkatut Jan 8, 2019
d5b7dc6
Merge pull request #302 from fadi-alkatut/master
pavelnikolov Jan 8, 2019
6bc8514
reverted changes in gitignore
0xSalman Jan 17, 2019
e3e3046
refactored code and documentation as per PR feedback
0xSalman Feb 4, 2019
e582242
Merge pull request #282 from salman-ahmad/master
pavelnikolov Feb 4, 2019
95e4a60
Merge pull request #300 from joefitzgerald/jf-convert-to-go-module
pavelnikolov Feb 5, 2019
70e684c
Merge pull request #296 from tinnywang/error-propagation
pavelnikolov Feb 14, 2019
fde50bb
Add DisableIntrospection SchemaOpt
stefanvanburen Feb 14, 2019
3e8838d
Merge pull request #309 from svanburen/master
pavelnikolov Feb 25, 2019
36edbf2
Fix parsing of descriptions
michaljemala Apr 5, 2019
eddaba1
Resolve schema parsing data races
dackroyd Apr 26, 2019
f4657ce
Fix Enum validation
dackroyd May 3, 2019
604dc33
Merge pull request #322 from dackroyd/bug/fix-data-race-on-schema-par…
pavelnikolov May 5, 2019
b068bc1
Merge pull request #326 from dackroyd/bug/fix-enum-validation
pavelnikolov May 5, 2019
c126f0e
Fix Panic on Introspection of Schema using ToJSON
dackroyd May 8, 2019
7ff14cf
Merge pull request #328 from dackroyd/bug/fix-panic-on-introspection-…
pavelnikolov May 11, 2019
4ef1910
Merge pull request #316 from michaljemala/master
pavelnikolov May 12, 2019
3572ff4
Fix Input Value Validation
dackroyd May 9, 2019
158e7b8
Merge pull request #330 from dackroyd/bug/fix-input-validation
pavelnikolov May 13, 2019
56aa86f
update opentracing-go version
gosp Jun 10, 2019
8f92f34
Merge pull request #336 from gosp/master
tonyghita Jun 10, 2019
35f2b75
Query Caching Example
dackroyd Jul 24, 2019
010347b
Merge pull request #343 from dackroyd/caching-example
pavelnikolov Jul 24, 2019
edfb465
Updated to Latest Version from graphql_gophers_master
Aug 8, 2019
a91d953
fix response feature in progress
Oct 25, 2018
527dd2f
fix response fixed
Oct 31, 2018
7a5f606
set default response for mutation fixed
Nov 19, 2018
d69a0b9
Updated graph-gophers to tokopedia in import paths
Aug 20, 2019
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
25 changes: 0 additions & 25 deletions Gopkg.lock

This file was deleted.

10 changes: 0 additions & 10 deletions Gopkg.toml

This file was deleted.

16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ safe for production use.
- resolvers are matched to the schema based on method sets (can resolve a GraphQL schema with a Go interface or Go struct).
- handles panics in resolvers
- parallel execution of resolvers
- subscriptions
- [sample WS transport](https://github.com/graph-gophers/graphql-transport-ws)

## Roadmap

Expand Down Expand Up @@ -63,7 +65,17 @@ $ curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query

### Resolvers

A resolver must have one method for each field of the GraphQL type it resolves. The method name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the field's name in a non-case-sensitive way.
A resolver must have one method or field for each field of the GraphQL type it resolves. The method or field name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the schema's field's name in a non-case-sensitive way.
You can use struct fields as resolvers by using `SchemaOpt: UseFieldResolvers()`. For example,
```
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers()}
schema := graphql.MustParseSchema(s, &query{}, opts...)
```

When using `UseFieldResolvers` schema option, a struct field will be used *only* when:
- there is no method for a struct field
- a struct field does not implement an interface method
- a struct field does not have arguments

The method has up to two arguments:

Expand Down Expand Up @@ -97,4 +109,4 @@ func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {

[deltaskelta/graphql-go-pets-example](https://github.com/deltaskelta/graphql-go-pets-example) - graphql-go resolving against a sqlite database

[OscarYuen/go-graphql-starter](https://github.com/OscarYuen/go-graphql-starter) - a starter application integrated with dataloader, psql and basic authenication
[OscarYuen/go-graphql-starter](https://github.com/OscarYuen/go-graphql-starter) - a starter application integrated with dataloader, psql and basic authentication
98 changes: 98 additions & 0 deletions example/caching/cache/hint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Package cache implements caching of GraphQL requests by allowing resolvers to provide hints about their cacheability,
// which can be used by the transport handlers (e.g. HTTP) to provide caching indicators in the response.
package cache

import (
"context"
"fmt"
"time"
)

type ctxKey string

const (
hintsKey ctxKey = "hints"
)

type scope int

// Cache control scopes.
const (
ScopePublic scope = iota
ScopePrivate
)

const (
hintsBuffer = 20
)

// Hint defines a hint as to how long something should be cached for.
type Hint struct {
MaxAge *time.Duration
Scope scope
}

// String resolves the HTTP Cache-Control value of the Hint.
func (h Hint) String() string {
var s string
switch h.Scope {
case ScopePublic:
s = "public"
case ScopePrivate:
s = "private"
}
return fmt.Sprintf("%s, max-age=%d", s, int(h.MaxAge.Seconds()))
}

// TTL defines the cache duration.
func TTL(d time.Duration) *time.Duration {
return &d
}

// AddHint applies a caching hint to the request context.
func AddHint(ctx context.Context, hint Hint) {
c := hints(ctx)
if c == nil {
return
}
c <- hint
}

// Hintable extends the context with the ability to add cache hints.
func Hintable(ctx context.Context) (hintCtx context.Context, hint <-chan Hint, done func()) {
hints := make(chan Hint, hintsBuffer)
h := make(chan Hint)
go func() {
h <- resolve(hints)
}()
done = func() {
close(hints)
}
return context.WithValue(ctx, hintsKey, hints), h, done
}

func hints(ctx context.Context) chan Hint {
h, ok := ctx.Value(hintsKey).(chan Hint)
if !ok {
return nil
}
return h
}

func resolve(hints <-chan Hint) Hint {
var minAge *time.Duration
s := ScopePublic
for h := range hints {
if h.Scope == ScopePrivate {
s = h.Scope
}
if h.MaxAge != nil && (minAge == nil || *h.MaxAge < *minAge) {
minAge = h.MaxAge
}
}
if minAge == nil {
var noCache time.Duration
minAge = &noCache
}
return Hint{MaxAge: minAge, Scope: s}
}
43 changes: 43 additions & 0 deletions example/caching/caching.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package caching

import (
"context"
"time"

"github.com/tokopedia/graphql-go/example/caching/cache"
)

const Schema = `
schema {
query: Query
}

type Query {
hello(name: String!): String!
me: UserProfile!
}

type UserProfile {
name: String!
}
`

type Resolver struct{}

func (r Resolver) Hello(ctx context.Context, args struct{ Name string }) string {
cache.AddHint(ctx, cache.Hint{MaxAge: cache.TTL(1 * time.Hour), Scope: cache.ScopePublic})
return "Hello " + args.Name + "!"
}

func (r Resolver) Me(ctx context.Context) *UserProfile {
cache.AddHint(ctx, cache.Hint{MaxAge: cache.TTL(1 * time.Minute), Scope: cache.ScopePrivate})
return &UserProfile{name: "World"}
}

type UserProfile struct {
name string
}

func (p *UserProfile) Name() string {
return p.name
}
139 changes: 139 additions & 0 deletions example/caching/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package main

import (
"encoding/json"
"fmt"
"log"
"net/http"

"github.com/tokopedia/graphql-go"
"github.com/tokopedia/graphql-go/example/caching"
"github.com/tokopedia/graphql-go/example/caching/cache"
)

var schema *graphql.Schema

func init() {
schema = graphql.MustParseSchema(caching.Schema, &caching.Resolver{})
}

func main() {
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(page)
}))

http.Handle("/query", &Handler{Schema: schema})

log.Fatal(http.ListenAndServe(":8080", nil))
}

type Handler struct {
Schema *graphql.Schema
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p, ok := h.parseRequest(w, r)
if !ok {
return
}
var response *graphql.Response
var hint *cache.Hint
if cacheable(r) {
ctx, hints, done := cache.Hintable(r.Context())
response = h.Schema.Exec(ctx, p.Query, p.OperationName, p.Variables)
done()
v := <-hints
hint = &v
} else {
response = h.Schema.Exec(r.Context(), p.Query, p.OperationName, p.Variables)
}
responseJSON, err := json.Marshal(response)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

if hint != nil {
w.Header().Set("Cache-Control", hint.String())
}
w.Header().Set("Content-Type", "application/json")
w.Write(responseJSON)
}

func (h *Handler) parseRequest(w http.ResponseWriter, r *http.Request) (params, bool) {
var p params
switch r.Method {
case http.MethodGet:
q := r.URL.Query()
if p.Query = q.Get("query"); p.Query == "" {
http.Error(w, "A non-empty 'query' parameter is required", http.StatusBadRequest)
return params{}, false
}
p.OperationName = q.Get("operationName")
if vars := q.Get("variables"); vars != "" {
if err := json.Unmarshal([]byte(vars), &p.Variables); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return params{}, false
}
}
return p, true
case http.MethodPost:
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return params{}, false
}
return p, true
default:
http.Error(w, fmt.Sprintf("unsupported HTTP method: %s", r.Method), http.StatusMethodNotAllowed)
return params{}, false
}
}

func cacheable(r *http.Request) bool {
return r.Method == http.MethodGet
}

type params struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"`
}

var page = []byte(`
<!DOCTYPE html>
<html>
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.js"></script>
</head>
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
<div id="graphiql" style="height: 100vh;">Loading...</div>
<script>
function graphQLFetcher(graphQLParams) {
const uri = "/query?query=" + encodeURIComponent(graphQLParams.query || "") + "&operationName=" + encodeURIComponent(graphQLParams.operationName || "") + "&variables=" + encodeURIComponent(graphQLParams.variables || "");
return fetch(uri, {
method: "get",
credentials: "include",
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}

ReactDOM.render(
React.createElement(GraphiQL, {fetcher: graphQLFetcher}),
document.getElementById("graphiql")
);
</script>
</body>
</html>
`)
9 changes: 9 additions & 0 deletions example/social/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### Social App

A simple example of how to use struct fields as resolvers instead of methods.

To run this server

`go run ./example/field-resolvers/server/server.go`

and go to localhost:9011 to interact
Loading