composite/batch/batch.go (129 lines of code) (raw):
package batch
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/elastic/go-sfdc"
"github.com/elastic/go-sfdc/session"
)
// Subrequester provides the composite batch API requests.
type Subrequester interface {
URL() string
Method() string
BinaryPartName() string
BinaryPartNameAlias() string
RichInput() map[string]interface{}
}
// Value is the returned structure from the composite batch API response.
type Value struct {
HasErrors bool `json:"hasErrors"`
Results []Subvalue `json:"results"`
}
// Subvalue is the subresponses to the composite batch API.
type Subvalue struct {
Result interface{} `json:"result"`
StatusCode int `json:"statusCode"`
}
const endpoint = "/composite/batch"
var validMethods = map[string]struct{}{
"PUT": {},
"POST": {},
"PATCH": {},
"GET": {},
"DELETE": {},
}
// Resource is the structure that can be just to call composite batch APIs.
type Resource struct {
session session.ServiceFormatter
}
// NewResource creates a new resourse with the session. If the session is
// nil an error will be returned.
func NewResource(session session.ServiceFormatter) (*Resource, error) {
if session == nil {
return nil, errors.New("composite: session can not be nil")
}
return &Resource{
session: session,
}, nil
}
// Retrieve will retrieve the responses to a composite batch requests. The
// order of the array is the order in which the subrequests are
// placed in the composite batch body.
func (r *Resource) Retrieve(haltOnError bool, requesters []Subrequester) (Value, error) {
if requesters == nil {
return Value{}, errors.New("composite subrequests: requesters can not nil")
}
err := r.validateSubrequests(requesters)
if err != nil {
return Value{}, err
}
body, err := r.payload(haltOnError, requesters)
if err != nil {
return Value{}, err
}
url := r.session.ServiceURL() + endpoint
request, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
return Value{}, err
}
request.Header.Add("Accept", "application/json")
request.Header.Add("Content-Type", "application/json")
r.session.AuthorizationHeader(request)
response, err := r.session.Client().Do(request)
if err != nil {
return Value{}, err
}
decoder := json.NewDecoder(response.Body)
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
var insertErrs []sfdc.Error
err = decoder.Decode(&insertErrs)
var errMsg error
if err == nil {
for _, insertErr := range insertErrs {
errMsg = fmt.Errorf("insert response err: %s: %s", insertErr.ErrorCode, insertErr.Message)
}
} else {
errMsg = fmt.Errorf("insert response err: %d %s", response.StatusCode, response.Status)
}
return Value{}, errMsg
}
var value Value
err = decoder.Decode(&value)
if err != nil {
return Value{}, err
}
return value, nil
}
func (r *Resource) validateSubrequests(requesters []Subrequester) error {
for _, requester := range requesters {
if requester.URL() == "" {
return errors.New("composite subrequest: must contain a url")
}
if _, has := validMethods[requester.Method()]; has == false {
return errors.New("composite subrequest: empty or invalid method " + requester.Method())
}
}
return nil
}
func (r *Resource) payload(haltOnError bool, requesters []Subrequester) (*bytes.Reader, error) {
subRequests := make([]interface{}, len(requesters))
for idx, requester := range requesters {
subRequest := map[string]interface{}{
"url": requester.URL(),
"method": requester.Method(),
}
if requester.BinaryPartName() != "" {
subRequest["binaryPartName"] = requester.BinaryPartName()
}
if requester.BinaryPartNameAlias() != "" {
subRequest["binaryPartNameAlias"] = requester.BinaryPartNameAlias()
}
if requester.RichInput() != nil {
subRequest["richInput"] = requester.RichInput()
}
subRequests[idx] = subRequest
}
payload := map[string]interface{}{
"haltOnError": haltOnError,
"batchRequests": subRequests,
}
jsonBody, err := json.Marshal(payload)
if err != nil {
return nil, err
}
return bytes.NewReader(jsonBody), nil
}