sobject/collections/collections.go (147 lines of code) (raw):

// Package collections is the implementation of the SObject Collections API. package collections import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "regexp" "github.com/elastic/go-sfdc" "github.com/elastic/go-sfdc/session" "github.com/elastic/go-sfdc/sobject" ) const ( endpoint = "/composite/sobjects" jsonContentType = "application/json" ) type collectionDmlPayload struct { AllOrNone bool `json:"allOrNone"` Records []interface{} `json:"records"` } type collection struct { method string endpoint string values *url.Values body io.Reader contentType string } // Resource is the structure for the SObject Collections API. type Resource struct { update *update query *query insert *insert remove *remove } // NewResources forms the Salesforce SObject Collections resource structure. The // session formatter is required to form the proper URLs and authorization // header. func NewResources(session session.ServiceFormatter) (*Resource, error) { if session == nil { return nil, errors.New("collections: session can not be nil") } return &Resource{ update: &update{ session: session, }, query: &query{ session: session, }, insert: &insert{ session: session, }, remove: &remove{ session: session, }, }, nil } // Insert will create a group of records in the Salesforce org. The records do not need to be // the same SObject. It is the responsibility of the caller to properly chunck the records. func (r *Resource) Insert(allOrNone bool, records []sobject.Inserter) ([]sobject.InsertValue, error) { if r.insert == nil { return nil, errors.New("collections resource: collections may not have been initialized properly") } if records == nil { return nil, errors.New("collections resource: insert records can not be nil") } return r.insert.callout(allOrNone, records) } // Delete will remove a group of records in the Salesforce org. The records do not need to // be the same SObject. func (r *Resource) Delete(allOrNone bool, records []string) ([]DeleteValue, error) { if r.remove == nil { return nil, errors.New("collections resource: collections may not have been initialized properly") } if records == nil { return nil, errors.New("collections resource: delete records can not be nil") } return r.remove.callout(allOrNone, records) } // Update will update a group of records in the Salesforce org. The records do not need to be // the same SObject. It is the responsibility of the caller to properly chunck the records. func (r *Resource) Update(allOrNone bool, records []sobject.Updater) ([]UpdateValue, error) { if r.update == nil { return nil, errors.New("collections resource: collections may not have been initialized properly") } if records == nil { return nil, errors.New("collections resource: update records can not be nil") } return r.update.callout(allOrNone, records) } // Query will retrieve a group of records from the Salesforce org. The records to retrieve must // be the same SObject. func (r *Resource) Query(sobject string, records []sobject.Querier) ([]*sfdc.Record, error) { if r.query == nil { return nil, errors.New("collections resource: collections may not have been initialized properly") } if records == nil { return nil, errors.New("collections resource: update records can not be nil") } matching, err := regexp.MatchString(`\w`, sobject) if err != nil { return nil, err } if matching == false { return nil, fmt.Errorf("collection resource: %s is not a valid sobject", sobject) } return r.query.callout(sobject, records) } func (c *collection) send(session session.ServiceFormatter, value interface{}) error { collectionURL := session.ServiceURL() + c.endpoint if c.values != nil { collectionURL += "?" + c.values.Encode() } request, err := http.NewRequest(c.method, collectionURL, c.body) if err != nil { return err } request.Header.Add("Accept", "application/json") if c.contentType != "" { request.Header.Add("Content-Type", c.contentType) } session.AuthorizationHeader(request) response, err := session.Client().Do(request) if err != nil { return 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 errMsg } err = decoder.Decode(value) if err != nil { return err } return nil } func dmlpayload(allOrNone bool, records []interface{}) (*bytes.Reader, error) { dmlPayload := collectionDmlPayload{ AllOrNone: allOrNone, Records: records, } payload, err := json.Marshal(dmlPayload) if err != nil { return nil, err } return bytes.NewReader(payload), nil }