-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathhttp.go
141 lines (112 loc) · 3.09 KB
/
http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package jaal
import (
"context"
"encoding/json"
"errors"
"net/http"
"go.appointy.com/jaal/graphql"
"go.appointy.com/jaal/jerrors"
)
type HandlerOption func(*handlerOptions)
type handlerOptions struct {
Middlewares []MiddlewareFunc
}
// HTTPHandler implements the handler required for executing the graphql queries and mutations
func HTTPHandler(schema *graphql.Schema, opts ...HandlerOption) http.Handler {
h := &httpHandler{
handler: handler{
schema: schema,
executor: &graphql.Executor{},
},
}
o := handlerOptions{}
for _, opt := range opts {
opt(&o)
}
prev := h.execute
for i := range o.Middlewares {
prev = o.Middlewares[len(o.Middlewares)-1-i](prev)
}
h.exec = prev
return h
}
type handler struct {
schema *graphql.Schema
executor *graphql.Executor
}
type httpHandler struct {
handler
exec HandlerFunc
}
type httpPostBody struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables"`
}
type httpResponse struct {
Data interface{} `json:"data"`
Errors []*jerrors.Error `json:"errors"`
}
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
writeResponse := func(value interface{}, err error) {
response := httpResponse{}
if err != nil {
response.Errors = []*jerrors.Error{jerrors.ConvertError(err)}
} else {
response.Data = value
}
responseJSON, err := json.Marshal(response)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if w.Header().Get("Content-Type") == "" {
w.Header().Set("Content-Type", "application/json")
}
_, _ = w.Write(responseJSON)
}
if r.Method != "POST" {
writeResponse(nil, errors.New("request must be a POST"))
return
}
if r.Body == nil {
writeResponse(nil, errors.New("request must include a query"))
return
}
var params httpPostBody
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
writeResponse(nil, err)
return
}
query, err := graphql.Parse(params.Query, params.Variables)
if err != nil {
writeResponse(nil, err)
return
}
root := h.schema.Query
if query.Kind == "mutation" {
root = h.schema.Mutation
}
if err := graphql.ValidateQuery(r.Context(), root, query.SelectionSet); err != nil {
writeResponse(nil, err)
return
}
ctx := addVariables(r.Context(), params.Variables)
output, err := h.exec(ctx, root, query)
writeResponse(output, err)
}
func (h *httpHandler) execute(ctx context.Context, root graphql.Type, query *graphql.Query) (interface{}, error) {
return h.executor.Execute(ctx, root, nil, query)
}
type graphqlVariableKeyType int
const graphqlVariableKey graphqlVariableKeyType = 0
// ExtractVariables is used to returns the variables received as part of the graphql request.
// This is intended to be used from within the interceptors.
func ExtractVariables(ctx context.Context) map[string]interface{} {
if v := ctx.Value(graphqlVariableKey); v != nil {
return v.(map[string]interface{})
}
return nil
}
func addVariables(ctx context.Context, v map[string]interface{}) context.Context {
return context.WithValue(ctx, graphqlVariableKey, v)
}