Skip to content

Commit e1362a2

Browse files
Added support changing the request payload format in graphQL API (#1526)
Co-authored-by: Noorain Panjwani <[email protected]>
1 parent feda40b commit e1362a2

File tree

3 files changed

+65
-27
lines changed

3 files changed

+65
-27
lines changed

gateway/config/config.go

+24-13
Original file line numberDiff line numberDiff line change
@@ -303,19 +303,24 @@ type Service struct {
303303

304304
// Endpoint holds the config of a endpoint
305305
type Endpoint struct {
306-
Kind EndpointKind `json:"kind" yaml:"kind" mapstructure:"kind"`
307-
Tmpl TemplatingEngine `json:"template,omitempty" yaml:"template,omitempty" mapstructure:"template"`
308-
ReqTmpl string `json:"requestTemplate" yaml:"requestTemplate" mapstructure:"requestTemplate"`
309-
GraphTmpl string `json:"graphTemplate" yaml:"graphTemplate" mapstructure:"graphTemplate"`
310-
ResTmpl string `json:"responseTemplate" yaml:"responseTemplate" mapstructure:"responseTemplate"`
311-
OpFormat string `json:"outputFormat,omitempty" yaml:"outputFormat,omitempty" mapstructure:"outputFormat"`
312-
Token string `json:"token,omitempty" yaml:"token,omitempty" mapstructure:"token"`
313-
Claims string `json:"claims,omitempty" yaml:"claims,omitempty" mapstructure:"claims"`
314-
Method string `json:"method" yaml:"method" mapstructure:"method"`
315-
Path string `json:"path" yaml:"path" mapstructure:"path"`
316-
Rule *Rule `json:"rule,omitempty" yaml:"rule,omitempty" mapstructure:"rule"`
317-
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers"`
318-
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout"` // Timeout is in seconds
306+
Kind EndpointKind `json:"kind" yaml:"kind" mapstructure:"kind"`
307+
Tmpl TemplatingEngine `json:"template,omitempty" yaml:"template,omitempty" mapstructure:"template"`
308+
// ReqPayloadFormat specifies the payload format
309+
// depending upon the payload format, the graphQL request that
310+
// gets converted to http request will use that format as it's payload
311+
// currently supported formats are application/json,multipart/form-data
312+
ReqPayloadFormat string `json:"requestPayloadFormat" yaml:"requestPayloadFormat" mapstructure:"requestPayloadFormat"`
313+
ReqTmpl string `json:"requestTemplate" yaml:"requestTemplate" mapstructure:"requestTemplate"`
314+
GraphTmpl string `json:"graphTemplate" yaml:"graphTemplate" mapstructure:"graphTemplate"`
315+
ResTmpl string `json:"responseTemplate" yaml:"responseTemplate" mapstructure:"responseTemplate"`
316+
OpFormat string `json:"outputFormat,omitempty" yaml:"outputFormat,omitempty" mapstructure:"outputFormat"`
317+
Token string `json:"token,omitempty" yaml:"token,omitempty" mapstructure:"token"`
318+
Claims string `json:"claims,omitempty" yaml:"claims,omitempty" mapstructure:"claims"`
319+
Method string `json:"method" yaml:"method" mapstructure:"method"`
320+
Path string `json:"path" yaml:"path" mapstructure:"path"`
321+
Rule *Rule `json:"rule,omitempty" yaml:"rule,omitempty" mapstructure:"rule"`
322+
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers"`
323+
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout"` // Timeout is in seconds
319324
}
320325

321326
// EndpointKind describes the type of endpoint. Default value - internal
@@ -330,6 +335,12 @@ const (
330335

331336
// EndpointKindPrepared describes an endpoint on on Space Cloud GraphQL layer
332337
EndpointKindPrepared EndpointKind = "prepared"
338+
339+
// EndpointRequestPayloadFormatJSON specifies json payload format for the request
340+
EndpointRequestPayloadFormatJSON string = "json"
341+
342+
// EndpointRequestPayloadFormatFormData specifies multipart/form-data payload format for the request
343+
EndpointRequestPayloadFormatFormData string = "form-data"
333344
)
334345

335346
// TemplatingEngine describes the type of endpoint. Default value - go

gateway/modules/functions/helpers.go

+39-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package functions
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"errors"
78
"fmt"
9+
"io"
10+
"mime/multipart"
811
"net/http"
912
"strconv"
1013
"strings"
@@ -136,7 +139,7 @@ func prepareHeaders(ctx context.Context, headers config.Headers, state map[strin
136139
return out
137140
}
138141

139-
func (m *Module) adjustReqBody(ctx context.Context, serviceID, endpointID, token string, endpoint *config.Endpoint, auth, params interface{}) (interface{}, error) {
142+
func (m *Module) adjustReqBody(ctx context.Context, serviceID, endpointID, token string, endpoint *config.Endpoint, auth, params interface{}) (io.Reader, error) {
140143
m.lock.RLock()
141144
defer m.lock.RUnlock()
142145

@@ -159,20 +162,51 @@ func (m *Module) adjustReqBody(ctx context.Context, serviceID, endpointID, token
159162
}
160163
default:
161164
helpers.Logger.LogWarn(helpers.GetRequestID(ctx), fmt.Sprintf("Invalid templating engine (%s) provided. Skipping templating step.", endpoint.Tmpl), map[string]interface{}{"serviceId": serviceID, "endpointId": endpointID})
162-
return params, nil
163165
}
164166

167+
var body interface{}
165168
switch endpoint.Kind {
166169
case config.EndpointKindInternal, config.EndpointKindExternal:
167170
if req == nil {
168-
return params, nil
171+
body = params
172+
} else {
173+
body = req
169174
}
170-
return req, nil
171175
case config.EndpointKindPrepared:
172-
return map[string]interface{}{"query": graph, "variables": req}, nil
176+
body = map[string]interface{}{"query": graph, "variables": req}
173177
default:
174178
return nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), fmt.Sprintf("Invalid endpoint kind (%s) provided", endpoint.Kind), nil, nil)
175179
}
180+
181+
var requestBody io.Reader
182+
switch endpoint.ReqPayloadFormat {
183+
case "", config.EndpointRequestPayloadFormatJSON:
184+
// Marshal json into byte array
185+
data, err := json.Marshal(body)
186+
if err != nil {
187+
return nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), fmt.Sprintf("Cannot marshal provided data for graphQL API endpoint (%s)", endpointID), err, map[string]interface{}{"serviceId": serviceID})
188+
}
189+
requestBody = bytes.NewReader(data)
190+
endpoint.Headers = append(endpoint.Headers, config.Header{Key: "Content-Type", Value: "application/json", Op: "set"})
191+
case config.EndpointRequestPayloadFormatFormData:
192+
buff := new(bytes.Buffer)
193+
writer := multipart.NewWriter(buff)
194+
195+
for key, val := range body.(map[string]interface{}) {
196+
value, ok := val.(string)
197+
if !ok {
198+
return nil, helpers.Logger.LogError(helpers.GetRequestID(ctx), fmt.Sprintf("Invalid type of value provided for arg (%s) expecting string as endpoint (%s) has request payload of (form-data) type ", endpointID, key), err, map[string]interface{}{"serviceId": serviceID})
199+
}
200+
_ = writer.WriteField(key, value)
201+
}
202+
err = writer.Close()
203+
if err != nil {
204+
return nil, err
205+
}
206+
requestBody = bytes.NewReader(buff.Bytes())
207+
endpoint.Headers = append(endpoint.Headers, config.Header{Key: "Content-Type", Value: writer.FormDataContentType(), Op: "set"})
208+
}
209+
return requestBody, err
176210
}
177211

178212
func (m *Module) adjustResBody(ctx context.Context, serviceID, endpointID, token string, endpoint *config.Endpoint, auth, params interface{}) (interface{}, error) {

gateway/utils/http.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package utils
22

33
import (
4-
"bytes"
54
"context"
65
"encoding/json"
76
"io"
@@ -16,7 +15,7 @@ type HTTPRequest struct {
1615
Headers headers
1716
Method, URL string
1817
Token, SCToken string
19-
Params interface{}
18+
Params io.Reader
2019
}
2120

2221
type headers interface {
@@ -25,18 +24,12 @@ type headers interface {
2524

2625
// MakeHTTPRequest fires an http request and returns a response
2726
func MakeHTTPRequest(ctx context.Context, request *HTTPRequest, vPtr interface{}) (int, error) {
28-
// Marshal json into byte array
29-
data, _ := json.Marshal(request.Params)
30-
3127
// Make a request object
32-
req, err := http.NewRequestWithContext(ctx, request.Method, request.URL, bytes.NewBuffer(data))
28+
req, err := http.NewRequestWithContext(ctx, request.Method, request.URL, request.Params)
3329
if err != nil {
3430
return http.StatusInternalServerError, err
3531
}
3632

37-
// Add the headers
38-
req.Header.Add("Content-Type", "application/json")
39-
4033
// Add the token only if its provided
4134
if request.Token != "" {
4235
req.Header.Add("Authorization", "Bearer "+request.Token)

0 commit comments

Comments
 (0)