-
Notifications
You must be signed in to change notification settings - Fork 556
/
graphql.go
239 lines (202 loc) · 5.99 KB
/
graphql.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// SPDX-License-Identifier: Apache-2.0
/*
Package graphql offers a param extractor and basic types for building GraphQL requests
*/
package graphql
import (
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"os"
"strings"
"github.com/luraproject/lura/v2/config"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
// Namespace is the key for the backend's extra config
const Namespace = "github.com/devopsfaith/krakend/transport/http/client/graphql"
// OperationType contains all the operations allowed by graphql
type OperationType string
// OperationMethod details the method to be used with the request
type OperationMethod string
const (
// OperationMutation marks an operation as a mutation
OperationMutation OperationType = "mutation"
// OperationQuery marks an operation as a query
OperationQuery OperationType = "query"
MethodPost OperationMethod = http.MethodPost
MethodGet OperationMethod = http.MethodGet
)
// GraphQLRequest represents the graphql request body
type GraphQLRequest struct {
Query string `json:"query"`
OperationName string `json:"operationName,omitempty"`
Variables map[string]interface{} `json:"variables,omitempty"`
}
// Options defines a GraphQLRequest with a type, so the middlewares know what to do
type Options struct {
GraphQLRequest
QueryPath string `json:"query_path,omitempty"`
Type OperationType `json:"type"`
Method OperationMethod `json:"method"`
}
var ErrNoConfigFound = errors.New("grapghql: no configuration found")
// GetOptions extracts the Options config from the backend's extra config
func GetOptions(cfg config.ExtraConfig) (*Options, error) {
tmp, ok := cfg[Namespace]
if !ok {
return nil, ErrNoConfigFound
}
b, err := json.Marshal(tmp)
if err != nil {
return nil, err
}
var opt Options
if err := json.Unmarshal(b, &opt); err != nil {
return nil, err
}
opt.Method = OperationMethod(strings.ToUpper(string(opt.Method)))
opt.Type = OperationType(strings.ToLower(string(opt.Type)))
if opt.Method != MethodGet && opt.Method != MethodPost {
opt.Method = MethodPost
}
if opt.QueryPath != "" {
q, err := os.ReadFile(opt.QueryPath)
if err != nil {
return nil, err
}
opt.Query = string(q)
}
return &opt, nil
}
// New resturns a new Extractor, ready to be use on a middleware
func New(opt Options) *Extractor {
var replacements [][2]string
title := cases.Title(language.Und)
for k, v := range opt.Variables {
val, ok := v.(string)
if !ok {
continue
}
if val[0] == '{' && val[len(val)-1] == '}' {
replacements = append(replacements, [2]string{k, title.String(val[1:2]) + val[2:len(val)-1]})
}
}
if len(replacements) == 0 {
b, _ := json.Marshal(opt.GraphQLRequest)
return &Extractor{
cfg: opt,
paramExtractor: func(map[string]string) (*GraphQLRequest, error) {
return &opt.GraphQLRequest, nil
},
newBody: func(_ map[string]string) ([]byte, error) {
return b, nil
},
}
}
paramExtractor := func(params map[string]string) (*GraphQLRequest, error) {
val := GraphQLRequest{
Query: opt.Query,
OperationName: opt.OperationName,
Variables: map[string]interface{}{},
}
for k, v := range opt.Variables {
val.Variables[k] = v
}
for _, vs := range replacements {
val.Variables[vs[0]] = params[vs[1]]
}
return &val, nil
}
return &Extractor{
cfg: opt,
paramExtractor: paramExtractor,
newBody: func(params map[string]string) ([]byte, error) {
val, err := paramExtractor(params)
if err != nil {
return []byte{}, err
}
return json.Marshal(val)
},
}
}
// Extractor exposes two extractor factories: one for the params (query) and one
// for the request body (mutator)
type Extractor struct {
cfg Options
paramExtractor func(map[string]string) (*GraphQLRequest, error)
newBody func(map[string]string) ([]byte, error)
}
// QueryFromBody returns a url.Values containing the graphql request with the given query and the default variables
// overiden by the request body
func (e *Extractor) QueryFromBody(r io.Reader) (url.Values, error) {
gr, err := e.fromBody(r)
if err != nil {
return nil, err
}
vars := url.Values{}
vars.Add("query", gr.Query)
if gr.OperationName != "" {
vars.Add("operationName", gr.OperationName)
}
if len(gr.Variables) != 0 {
encodedVars, _ := json.Marshal(gr.Variables)
vars.Add("variables", string(encodedVars))
}
return vars, nil
}
// BodyFromBody returns a request body containing the graphql request with the given query and the default variables
// overiden by the request body
func (e *Extractor) BodyFromBody(r io.Reader) ([]byte, error) {
v, err := e.fromBody(r)
if err != nil {
return []byte{}, err
}
return json.Marshal(v)
}
func (e *Extractor) fromBody(r io.Reader) (*GraphQLRequest, error) {
b, err := io.ReadAll(r)
if err != nil {
return nil, err
}
vars := map[string]interface{}{}
if err := json.Unmarshal(b, &vars); err != nil {
return nil, err
}
for k, v := range e.cfg.Variables {
if _, ok := vars[k]; ok {
continue
}
vars[k] = v
}
return &GraphQLRequest{
Query: e.cfg.Query,
OperationName: e.cfg.OperationName,
Variables: vars,
}, nil
}
// QueryFromParams returns a url.Values containing the grapql request generated for the given query and the default
// variables overiden by the request params
func (e *Extractor) QueryFromParams(params map[string]string) (url.Values, error) {
gr, err := e.paramExtractor(params)
if err != nil {
return nil, err
}
vars := url.Values{}
vars.Add("query", gr.Query)
if gr.OperationName != "" {
vars.Add("operationName", gr.OperationName)
}
if len(gr.Variables) != 0 {
encodedVars, _ := json.Marshal(gr.Variables)
vars.Add("variables", string(encodedVars))
}
return vars, nil
}
// BodyFromParams returns a request body containing the grapql request generated for the given query and the default
// variables overiden by the request params
func (e *Extractor) BodyFromParams(params map[string]string) ([]byte, error) {
return e.newBody(params)
}