Skip to content

Commit

Permalink
Merge pull request #4 from antosdaniel/updating-from-official-repo
Browse files Browse the repository at this point in the history
update from official repo
  • Loading branch information
Hagbarth authored May 5, 2021
2 parents 5b61b0c + 16e9d29 commit 40361a1
Show file tree
Hide file tree
Showing 58 changed files with 11,339 additions and 1,662 deletions.
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Contributing

- With issues:
- Use the search tool before opening a new issue.
- Please provide source code and commit sha if you found a bug.
- Review existing issues and provide feedback or react to them.

- With pull requests:
- Open your pull request against `master`
- Your pull request should have no more than two commits, if not you should squash them.
- It should pass all tests in the available continuous integrations systems such as TravisCI.
- You should add/modify tests to cover your proposed code changes.
- If your pull request contains a new feature, please document it on the README.
27 changes: 0 additions & 27 deletions Gopkg.lock

This file was deleted.

10 changes: 0 additions & 10 deletions Gopkg.toml

This file was deleted.

118 changes: 111 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# graphql-go [![Sourcegraph](https://sourcegraph.com/github.com/PentoHQ/graphql-go/-/badge.svg)](https://sourcegraph.com/github.com/PentoHQ/graphql-go?badge) [![Build Status](https://semaphoreci.com/api/v1/graph-gophers/graphql-go/branches/master/badge.svg)](https://semaphoreci.com/graph-gophers/graphql-go) [![GoDoc](https://godoc.org/github.com/PentoHQ/graphql-go?status.svg)](https://godoc.org/github.com/PentoHQ/graphql-go)
# graphql-go [![Sourcegraph](https://sourcegraph.com/github.com/graph-gophers/graphql-go/-/badge.svg)](https://sourcegraph.com/github.com/graph-gophers/graphql-go?badge) [![Build Status](https://semaphoreci.com/api/v1/graph-gophers/graphql-go/branches/master/badge.svg)](https://semaphoreci.com/graph-gophers/graphql-go) [![GoDoc](https://godoc.org/github.com/graph-gophers/graphql-go?status.svg)](https://godoc.org/github.com/graph-gophers/graphql-go)

<p align="center"><img src="docs/img/logo.png" width="300"></p>

Expand All @@ -16,17 +16,63 @@ 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

We're trying out the GitHub Project feature to manage `graphql-go`'s [development roadmap](https://github.com/PentoHQ/graphql-go/projects/1).
We're trying out the GitHub Project feature to manage `graphql-go`'s [development roadmap](https://github.com/graph-gophers/graphql-go/projects/1).
Feedback is welcome and appreciated.

## (Some) Documentation

### Basic Sample

```go
package main

import (
"log"
"net/http"

graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)

type query struct{}

func (_ *query) Hello() string { return "Hello, world!" }

func main() {
s := `
type Query {
hello: String!
}
`
schema := graphql.MustParseSchema(s, &query{})
http.Handle("/query", &relay.Handler{Schema: schema})
log.Fatal(http.ListenAndServe(":8080", nil))
}
```

To test:
```sh
$ 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 @@ -54,10 +100,68 @@ func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {
}
```

### Community Examples
### Schema Options

- `UseStringDescriptions()` enables the usage of double quoted and triple quoted. When this is not enabled, comments are parsed as descriptions instead.
- `UseFieldResolvers()` specifies whether to use struct field resolvers.
- `MaxDepth(n int)` specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.
- `MaxParallelism(n int)` specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.
- `Tracer(tracer trace.Tracer)` is used to trace queries and fields. It defaults to `trace.OpenTracingTracer`.
- `ValidationTracer(tracer trace.ValidationTracer)` is used to trace validation errors. It defaults to `trace.NoopValidationTracer`.
- `Logger(logger log.Logger)` is used to log panics during query execution. It defaults to `exec.DefaultLogger`.
- `DisableIntrospection()` disables introspection queries.

### Custom Errors

Errors returned by resolvers can include custom extensions by implementing the `ResolverError` interface:

```go
type ResolverError interface {
error
Extensions() map[string]interface{}
}
```

Example of a simple custom error:

[tonyghita/graphql-go-example](https://github.com/tonyghita/graphql-go-example) - A more "productionized" version of the Star Wars API example given in this repository.
```go
type droidNotFoundError struct {
Code string `json:"code"`
Message string `json:"message"`
}

func (e droidNotFoundError) Error() string {
return fmt.Sprintf("error [%s]: %s", e.Code, e.Message)
}

func (e droidNotFoundError) Extensions() map[string]interface{} {
return map[string]interface{}{
"code": e.Code,
"message": e.Message,
}
}
```

Which could produce a GraphQL error such as:

```go
{
"errors": [
{
"message": "error [NotFound]: This is not the droid you are looking for",
"path": [
"droid"
],
"extensions": {
"code": "NotFound",
"message": "This is not the droid you are looking for"
}
}
],
"data": null
}
```

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

[OscarYuen/go-graphql-starter](https://github.com/OscarYuen/go-graphql-starter) - a starter application integrated with dataloader, psql and basic authenication
### [Companies that use this library](https://github.com/graph-gophers/graphql-go/wiki/Users)
11 changes: 6 additions & 5 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import (
)

type QueryError struct {
Message string `json:"message"`
Locations []Location `json:"locations,omitempty"`
Path []interface{} `json:"path,omitempty"`
Rule string `json:"-"`
ResolverError error `json:"-"`
Message string `json:"message"`
Locations []Location `json:"locations,omitempty"`
Path []interface{} `json:"path,omitempty"`
Rule string `json:"-"`
ResolverError error `json:"-"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
}

type Location struct {
Expand Down
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/graph-gophers/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
}
Loading

0 comments on commit 40361a1

Please sign in to comment.