sobject/dml.go (219 lines of code) (raw):
package sobject
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/elastic/go-sfdc"
"github.com/elastic/go-sfdc/session"
)
// InsertValue is the value that is returned when a
// record is inserted into Salesforce.
type InsertValue struct {
Success bool `json:"success"`
ID string `json:"id"`
Errors []sfdc.Error `json:"errors"`
}
// UpsertValue is the value that is return when a
// record as been upserted into Salesforce.
//
// Upsert will return two types of values, which
// are indicated by Inserted. If Inserted is true,
// then the InsertValue is popluted.
type UpsertValue struct {
Inserted bool
InsertValue
}
// Inserter provides the parameters needed insert a record.
//
// SObject is the Salesforce table name. An example would be Account or Custom__c.
//
// Fields are the fields of the record that are to be inserted. It is the
// callers responsbility to provide value fields and values.
type Inserter interface {
SObject() string
Fields() map[string]interface{}
}
// Updater provides the parameters needed to update a record.
//
// SObject is the Salesforce table name. An example would be Account or Custom__c.
//
// ID is the Salesforce ID that will be updated.
//
// Fields are the fields of the record that are to be inserted. It is the
// callers responsbility to provide value fields and values.
type Updater interface {
Inserter
ID() string
}
// Upserter provides the parameters needed to upsert a record.
//
// SObject is the Salesforce table name. An example would be Account or Custom__c.
//
// ID is the External ID that will be updated.
//
// ExternalField is the external ID field.
//
// Fields are the fields of the record that are to be inserted. It is the
// callers responsbility to provide value fields and values.
type Upserter interface {
Updater
ExternalField() string
}
// Deleter provides the parameters needed to delete a record.
//
// SObject is the Salesforce table name. An example would be Account or Custom__c.
//
// ID is the Salesforce ID to be deleted.
type Deleter interface {
SObject() string
ID() string
}
type dml struct {
session session.ServiceFormatter
}
func (d *dml) insertCallout(inserter Inserter) (InsertValue, error) {
request, err := d.insertRequest(inserter)
if err != nil {
return InsertValue{}, err
}
value, err := d.insertResponse(request)
if err != nil {
return InsertValue{}, err
}
return value, nil
}
func (d *dml) insertRequest(inserter Inserter) (*http.Request, error) {
url := d.session.ServiceURL() + objectEndpoint + inserter.SObject()
body, err := json.Marshal(inserter.Fields())
if err != nil {
return nil, err
}
request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return nil, err
}
request.Header.Add("Accept", "application/json")
request.Header.Add("Content-Type", "application/json")
d.session.AuthorizationHeader(request)
return request, nil
}
func (d *dml) insertResponse(request *http.Request) (InsertValue, error) {
response, err := d.session.Client().Do(request)
if err != nil {
return InsertValue{}, err
}
decoder := json.NewDecoder(response.Body)
defer response.Body.Close()
if response.StatusCode != http.StatusCreated {
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 InsertValue{}, errMsg
}
var value InsertValue
err = decoder.Decode(&value)
if err != nil {
return InsertValue{}, err
}
return value, nil
}
func (d *dml) updateCallout(updater Updater) error {
request, err := d.updateRequest(updater)
if err != nil {
return err
}
return d.updateResponse(request)
}
func (d *dml) updateRequest(updater Updater) (*http.Request, error) {
url := d.session.ServiceURL() + objectEndpoint + updater.SObject() + "/" + updater.ID()
body, err := json.Marshal(updater.Fields())
if err != nil {
return nil, err
}
request, err := http.NewRequest(http.MethodPatch, url, bytes.NewReader(body))
if err != nil {
return nil, err
}
request.Header.Add("Accept", "application/json")
request.Header.Add("Content-Type", "application/json")
d.session.AuthorizationHeader(request)
return request, nil
}
func (d *dml) updateResponse(request *http.Request) error {
response, err := d.session.Client().Do(request)
if err != nil {
return err
}
if response.StatusCode != http.StatusNoContent {
decoder := json.NewDecoder(response.Body)
defer response.Body.Close()
var updateErrs []sfdc.Error
err = decoder.Decode(&updateErrs)
var errMsg error
if err == nil {
for _, updateErr := range updateErrs {
errMsg = fmt.Errorf("insert response err: %s: %s", updateErr.ErrorCode, updateErr.Message)
}
} else {
errMsg = fmt.Errorf("insert response err: %d %s", response.StatusCode, response.Status)
}
return errMsg
}
return nil
}
func (d *dml) upsertCallout(upserter Upserter) (UpsertValue, error) {
request, err := d.upsertRequest(upserter)
if err != nil {
return UpsertValue{}, err
}
value, err := d.upsertResponse(request)
if err != nil {
return UpsertValue{}, err
}
return value, nil
}
func (d *dml) upsertRequest(upserter Upserter) (*http.Request, error) {
url := d.session.ServiceURL() + objectEndpoint + upserter.SObject() + "/" + upserter.ExternalField() + "/" + upserter.ID()
body, err := json.Marshal(upserter.Fields())
if err != nil {
return nil, err
}
request, err := http.NewRequest(http.MethodPatch, url, bytes.NewReader(body))
if err != nil {
return nil, err
}
request.Header.Add("Accept", "application/json")
request.Header.Add("Content-Type", "application/json")
d.session.AuthorizationHeader(request)
return request, nil
}
func (d *dml) upsertResponse(request *http.Request) (UpsertValue, error) {
response, err := d.session.Client().Do(request)
if err != nil {
return UpsertValue{}, err
}
decoder := json.NewDecoder(response.Body)
var isInsert bool
var value UpsertValue
switch response.StatusCode {
case http.StatusCreated:
defer response.Body.Close()
isInsert = true
err = decoder.Decode(&value)
if err != nil {
return UpsertValue{}, err
}
case http.StatusNoContent:
isInsert = false
default:
defer response.Body.Close()
var upsetErrs []sfdc.Error
err = decoder.Decode(&upsetErrs)
errMsg := fmt.Errorf("upsert response err: %d %s", response.StatusCode, response.Status)
if err == nil {
for _, updateErr := range upsetErrs {
errMsg = fmt.Errorf("upsert response err: %s: %s", updateErr.ErrorCode, updateErr.Message)
}
}
return UpsertValue{}, errMsg
}
value.Inserted = isInsert
return value, nil
}
func (d *dml) deleteCallout(deleter Deleter) error {
request, err := d.deleteRequest(deleter)
if err != nil {
return err
}
return d.deleteResponse(request)
}
func (d *dml) deleteRequest(deleter Deleter) (*http.Request, error) {
url := d.session.ServiceURL() + objectEndpoint + deleter.SObject() + "/" + deleter.ID()
request, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return nil, err
}
d.session.AuthorizationHeader(request)
return request, nil
}
func (d *dml) deleteResponse(request *http.Request) error {
response, err := d.session.Client().Do(request)
if err != nil {
return err
}
if response.StatusCode != http.StatusNoContent {
return fmt.Errorf("delete has failed %d %s", response.StatusCode, response.Status)
}
return nil
}