Skip to content

Commit

Permalink
Merge pull request #28 from rigglo/subscriptions
Browse files Browse the repository at this point in the history
Subscriptions implementation
  • Loading branch information
Fontinalis authored May 19, 2020
2 parents 6488c23 + c18a8e5 commit 0138a6d
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 88 deletions.
4 changes: 2 additions & 2 deletions examples/custommarshaler/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (

func main() {
h := handler.New(handler.Config{
Executor: gql.DefaultExecutor(Schema),
GraphiQL: true,
Executor: gql.DefaultExecutor(Schema),
Playground: true,
})
http.Handle("/graphql", h)
if err := http.ListenAndServe(":9999", nil); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions examples/directives/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (

func main() {
h := handler.New(handler.Config{
Executor: gql.DefaultExecutor(Schema),
GraphiQL: true,
Executor: gql.DefaultExecutor(Schema),
Playground: true,
})
http.Handle("/graphql", h)
if err := http.ListenAndServe(":9999", nil); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions examples/extension/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ func main() {
})

h := handler.New(handler.Config{
Executor: exec,
GraphiQL: true,
Executor: exec,
Playground: true,
})
http.Handle("/graphql", h)
if err := http.ListenAndServe(":9999", nil); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions examples/movies/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (

func main() {
h := handler.New(handler.Config{
Executor: gql.DefaultExecutor(BlockBusters),
GraphiQL: true,
Executor: gql.DefaultExecutor(BlockBusters),
Playground: true,
})
http.Handle("/graphql", h)
if err := http.ListenAndServe(":9999", nil); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions examples/pets/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ func init() {

func main() {
h := handler.New(handler.Config{
Executor: gql.DefaultExecutor(PetStore),
GraphiQL: true,
Executor: gql.DefaultExecutor(PetStore),
Playground: true,
})
http.Handle("/graphql", h)
if err := http.ListenAndServe(":9999", nil); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions examples/pizzeria/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

func main() {
http.Handle("/graphql", handler.New(handler.Config{
Executor: gql.DefaultExecutor(Schema),
GraphiQL: true,
Executor: gql.DefaultExecutor(Schema),
Playground: true,
}))
if err := http.ListenAndServe(":9999", nil); err != nil {
panic(err)
Expand Down
95 changes: 93 additions & 2 deletions execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,45 @@ func (e *Executor) Execute(ctx context.Context, p Params) *Result {
return gqlctx.res
}

func (e *Executor) Subscribe(ctx context.Context, query string, operationName string, variables map[string]interface{}) (<-chan interface{}, error) {
if e.config.Schema.Subscription == nil {
return nil, errors.New("Schema does not provide subscriptions")
}
p := Params{
Query: query,
OperationName: operationName,
Variables: variables,
}

doc, err := parser.Parse([]byte(p.Query))
if err != nil {
return nil, err
}
types, directives, implementors := getTypes(e.config.Schema)

gqlctx := newContext(ctx, e.config.Schema, doc, &p, e.config.GoroutineLimit, e.config.EnableGoroutines)
gqlctx.types = types
gqlctx.directives = directives
gqlctx.implementors = implementors

validate(gqlctx)
if len(gqlctx.res.Errors) > 0 {
return nil, errors.New("validation error: invalid document")
}

getOperation(gqlctx)
if len(gqlctx.res.Errors) > 0 {
return nil, errors.New("invalid operation")
}

coerceVariableValues(gqlctx)
if len(gqlctx.res.Errors) > 0 {
return nil, errors.New("invalid variables")
}

return subscribe(gqlctx)
}

type Params struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables"`
Expand Down Expand Up @@ -282,7 +321,7 @@ func resolveOperation(ctx *gqlCtx) {
}

func executeQuery(ctx *gqlCtx, op *ast.Operation) *Result {
rmap, hasNullErrs := executeSelectionSet(ctx, []interface{}{}, op.SelectionSet, ctx.schema.Query, nil)
rmap, hasNullErrs := executeSelectionSet(ctx, []interface{}{}, op.SelectionSet, ctx.schema.Query, ctx.schema.RootValue)
if hasNullErrs {
ctx.res.Data = nil
} else {
Expand All @@ -292,7 +331,7 @@ func executeQuery(ctx *gqlCtx, op *ast.Operation) *Result {
}

func executeMutation(ctx *gqlCtx, op *ast.Operation) *Result {
rmap, hasNullErrs := executeSelectionSet(ctx, []interface{}{}, op.SelectionSet, ctx.schema.Mutation, nil)
rmap, hasNullErrs := executeSelectionSet(ctx, []interface{}{}, op.SelectionSet, ctx.schema.Mutation, ctx.schema.RootValue)
if hasNullErrs {
ctx.res.Data = nil
} else {
Expand All @@ -301,6 +340,55 @@ func executeMutation(ctx *gqlCtx, op *ast.Operation) *Result {
return ctx.res
}

func subscribe(ctx *gqlCtx) (<-chan interface{}, error) {
gfields := collectFields(ctx, ctx.schema.Subscription, ctx.operation.SelectionSet, nil)

out := make(chan interface{})

// Since the subscription operations must have ONE selection ONLY
// it's not a problem to run the field.Subscribe function serially
for rkey, fs := range gfields {
fieldName := fs[0].Name
if !strings.HasPrefix(fieldName, "__") {
res, err := ctx.schema.Subscription.Fields[fieldName].Resolver(
&resolveContext{
ctx: ctx.ctx, // this is the original context
gqlCtx: ctx, // execution context
args: coerceArgumentValues(ctx, []interface{}{rkey}, ctx.schema.Subscription, fs[0]),
parent: ctx.schema.RootValue, // root value
path: []interface{}{rkey},
},
)
if err != nil {
return nil, err
}

if ch, ok := res.(chan interface{}); ok {
go func() {
for v := range ch {
res, hasErr := completeValue(ctx, nil, ctx.schema.Subscription.Fields[fieldName].Type, fs, v)
if hasErr {
out <- &Result{
Data: nil,
Errors: ctx.res.Errors,
}
ctx.res.Errors = []*Error{}
}
out <- &Result{
Data: map[string]interface{}{
rkey: res,
},
}
}
close(out)
}()
}
return out, nil
}
}
return nil, errors.New("invalid subscription")
}

func executeSelectionSet(ctx *gqlCtx, path []interface{}, ss []ast.Selection, ot *Object, ov interface{}) (map[string]interface{}, bool) {
gfields := collectFields(ctx, ot, ss, nil)
resMap := map[string]interface{}{}
Expand Down Expand Up @@ -933,6 +1021,9 @@ func getTypes(s *Schema) (map[string]Type, map[string]Directive, map[string][]Ty
if s.Mutation != nil {
typeWalker(types, directives, implementors, s.Mutation)
}
if s.Subscription != nil {
typeWalker(types, directives, implementors, s.Subscription)
}
return types, directives, implementors
}

Expand Down
5 changes: 4 additions & 1 deletion introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ func init() {
Type: NewList(NewNonNull(typeIntrospection)),
Resolver: func(ctx Context) (interface{}, error) {
if ctx.Parent().(Type).GetKind() == ObjectKind {
return ctx.Parent().(*Object).GetInterfaces(), nil
if ctx.Parent().(*Object).Implements == nil {
return []struct{}{}, nil
}
return ctx.Parent().(*Object).Implements, nil
}
return nil, nil
},
Expand Down
65 changes: 0 additions & 65 deletions pkg/handler/graphiql.go

This file was deleted.

16 changes: 8 additions & 8 deletions pkg/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
)

type Config struct {
Executor *gql.Executor
GraphiQL bool
Pretty bool
Executor *gql.Executor
Playground bool
Pretty bool
}

func New(c Config) http.Handler {
Expand All @@ -31,6 +31,11 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
{
if h.conf.Playground {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, playground)
return
}
params = &gql.Params{
Query: html.UnescapeString(r.URL.Query().Get("query")),
Variables: map[string]interface{}{}, // TODO: find a way of doing this..
Expand All @@ -44,11 +49,6 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
}
if h.conf.GraphiQL {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, graphiql)
return
}
}
case http.MethodPost:
{
Expand Down
64 changes: 64 additions & 0 deletions pkg/handler/playground.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package handler

const playground = `
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8/>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<title>GraphQL Playground</title>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/graphql-playground-react/build/static/css/index.css" />
<link rel="shortcut icon" href="//cdn.jsdelivr.net/npm/graphql-playground-react/build/favicon.png" />
<script src="//cdn.jsdelivr.net/npm/graphql-playground-react/build/static/js/middleware.js"></script>
</head>
<body>
<div id="root">
<style>
body {
background-color: rgb(23, 42, 58);
font-family: Open Sans, sans-serif;
height: 90vh;
}
#root {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.loading {
font-size: 32px;
font-weight: 200;
color: rgba(255, 255, 255, .6);
margin-left: 20px;
}
img {
width: 78px;
height: 78px;
}
.title {
font-weight: 400;
}
</style>
<img src='//cdn.jsdelivr.net/npm/graphql-playground-react/build/logo.png' alt=''>
<div class="loading"> Loading
<span class="title">GraphQL Playground</span>
</div>
</div>
<script>window.addEventListener('load', function (event) {
var rootURL = window
GraphQLPlayground.init(document.getElementById('root'), {
// options as 'endpoint' belong here
endpoint: window.location.protocol + "//" + window.location.host + window.location.pathname,
subscriptionEndpoint: "ws://" + window.location.host + window.location.pathname
})
})</script>
</body>
</html>`
1 change: 1 addition & 0 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Schema struct {
Mutation *Object
Subscription *Object
Directives Directives
RootValue interface{}
}

// TypeKind shows the kind of a Type
Expand Down

0 comments on commit 0138a6d

Please sign in to comment.