github.com/jfcote87/salesforce@v0.1.0/salesforce.go (about)

     1  // Copyright 2022 James Cote
     2  // All rights reserved.
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  // Package salesforce implements data access, creation and updating
     7  // routines for the Salesforce Rest API
     8  // https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest.htm
     9  package salesforce // import github.org/jfcote87/salesforce
    10  
    11  import (
    12  	"bytes"
    13  	"encoding/json"
    14  	"fmt"
    15  	"reflect"
    16  	"sync"
    17  	"time"
    18  )
    19  
    20  // QueryResponse is the base response for a query
    21  type QueryResponse struct {
    22  	TotalSize      int          `json:"totalSize,omitempty"`
    23  	Done           bool         `json:"done,omitempty"`
    24  	NextRecordsURL string       `json:"nextRecordsUrl,omitempty"`
    25  	Records        *RecordSlice `json:"records"`
    26  }
    27  
    28  // RecordSlice wraps pointer to the result slice allowing
    29  // custom unmarshaling that adds rows through multiple
    30  // next record calls
    31  type RecordSlice struct {
    32  	resultsVal  reflect.Value
    33  	resultsType reflect.Type
    34  }
    35  
    36  func (rs *RecordSlice) rows() int {
    37  	return rs.resultsVal.Len()
    38  }
    39  
    40  // slice resets the value of resultsVal to resultsVal.Interface()[i:j]
    41  func (rs *RecordSlice) slice(i, j int) {
    42  	rs.resultsVal.Set(rs.resultsVal.Slice(i, j))
    43  }
    44  
    45  // NewRecordSlice creates a RecordSlice pointer based upon *[]<struct> of the results
    46  // parameter; error created when result is an invalid type
    47  func NewRecordSlice(results interface{}) (*RecordSlice, error) {
    48  	ptr := reflect.ValueOf(results)
    49  	pType := ptr.Type()
    50  	if pType.Kind() != reflect.Ptr || pType.Elem().Kind() != reflect.Slice {
    51  		return nil, fmt.Errorf("expected *[]<struct>; got %v", ptr.Type())
    52  	}
    53  	slice := ptr.Elem()
    54  	return &RecordSlice{
    55  		resultsVal:  slice,
    56  		resultsType: slice.Type(),
    57  	}, nil
    58  }
    59  
    60  // UnmarshalJSON creates a temporary slice for unmarshaling a call.  This temp
    61  // slice is then appended to the resultsVal
    62  func (rs *RecordSlice) UnmarshalJSON(b []byte) error {
    63  	if rs == nil || reflect.ValueOf(rs.resultsVal).IsZero() {
    64  		return fmt.Errorf("uninitialized QueryResult")
    65  	}
    66  	tempSlice := reflect.New(rs.resultsType)
    67  	// initialize tempSlice
    68  	tempSlice.Elem().Set(reflect.AppendSlice(reflect.MakeSlice(rs.resultsType, 0, defaultBatchSize), tempSlice.Elem()))
    69  	if err := json.Unmarshal(b, tempSlice.Interface()); err != nil {
    70  		return err
    71  	}
    72  
    73  	rs.resultsVal.Set(reflect.AppendSlice(rs.resultsVal, tempSlice.Elem()))
    74  	return nil
    75  }
    76  
    77  // MarshalJSON marshals the value in resultsVal
    78  func (rs RecordSlice) MarshalJSON() ([]byte, error) {
    79  	if !rs.resultsVal.IsNil() && rs.resultsVal.CanInterface() {
    80  		return json.Marshal(rs.resultsVal.Interface())
    81  	}
    82  	return nil, nil
    83  }
    84  
    85  const defaultDatetimeFormat = "2006-01-02T15:04:05.000Z0700"
    86  const defaultDateFormat = "2006-01-02"
    87  
    88  // Time converts the string to a time.Time value
    89  func (d *Datetime) Time() *time.Time {
    90  	if d == nil || *d == "" {
    91  		return nil
    92  	}
    93  	tm, err := time.Parse(defaultDatetimeFormat, string(*d))
    94  	if err != nil || tm.IsZero() {
    95  		return nil
    96  	}
    97  	return &tm
    98  
    99  }
   100  
   101  // Time converts the string to a time.Time value
   102  func (d *Date) Time() *time.Time {
   103  	if d == nil || *d == "" {
   104  		return nil
   105  	}
   106  	tm, err := time.Parse(defaultDateFormat, string(*d))
   107  	if err != nil || tm.IsZero() {
   108  		return nil
   109  	}
   110  	return &tm
   111  
   112  }
   113  
   114  // TmToDate converts a time.Time to a Date pointer with zero time value
   115  func TmToDate(tm *time.Time) *Date {
   116  	if tm != nil && !tm.IsZero() {
   117  		dt := Date(tm.Format("2006-01-02"))
   118  		return &dt
   119  	}
   120  	return nil
   121  }
   122  
   123  // TmToDatetime converts a time.Time to DateTime
   124  func TmToDatetime(tm *time.Time) *Datetime {
   125  	if tm != nil && !tm.IsZero() {
   126  		dt := Datetime(tm.Format("2006-01-02T15:04:05.000Z0700"))
   127  		return &dt
   128  	}
   129  	return nil
   130  }
   131  
   132  // Date handles the json marshaling and unmarshaling of SF date type which
   133  // is a string of format yyyy-mm-dd
   134  type Date string
   135  
   136  // Datetime  handles the json marshaling and unmarshaling of SF datetime type
   137  // which a string of iso 8061 format yyyy-mm-ddThh:mm:ss.sss+0000
   138  type Datetime string
   139  
   140  // Time handles the json marshaling and unmarshaling of SF time type
   141  type Time string
   142  
   143  // MarshalText handles outputting json of date.  Empty value
   144  // outputs null, a nil ptr is omitted with omitempty.
   145  func (d *Datetime) MarshalText() ([]byte, error) {
   146  	if d != nil && *d > "" {
   147  		return []byte(*d), nil
   148  	}
   149  	return nil, nil
   150  }
   151  
   152  // UnmarshalText does null handling during json decode,
   153  func (d *Datetime) UnmarshalText(b []byte) error {
   154  	if d == nil {
   155  		return fmt.Errorf("nil pointer")
   156  	}
   157  	*d = Datetime(b)
   158  	return nil
   159  }
   160  
   161  // Display allows different formatting of *Date
   162  // and displays nils and empty strings
   163  func (d *Date) Display(format string) string {
   164  	tm := d.Time()
   165  	if tm == nil {
   166  		return ""
   167  	}
   168  	if format == "" {
   169  		format = "2006-01-02"
   170  	}
   171  	return tm.Format(format)
   172  }
   173  
   174  // MarshalText handles outputting json of date.  Empty value
   175  // outputs null, a nil ptr is omitted with omitempty.
   176  func (d Date) MarshalText() ([]byte, error) {
   177  	if d > "" {
   178  		return []byte(d), nil
   179  	}
   180  	return nil, nil
   181  }
   182  
   183  // UnmarshalText does null handling during json decode,
   184  func (d *Date) UnmarshalText(b []byte) error {
   185  	if d == nil {
   186  		return fmt.Errorf("nil pointer")
   187  	}
   188  	*d = Date(b)
   189  	return nil
   190  }
   191  
   192  // MarshalText handles outputting json of time.  Empty value
   193  // outputs null, a nil ptr is omitted with omitempty.
   194  func (t Time) MarshalText() ([]byte, error) {
   195  	if t > "" {
   196  		return []byte(t), nil
   197  	}
   198  	return []byte("null"), nil
   199  }
   200  
   201  // UnmarshalText does null handling during json decode,
   202  func (t *Time) UnmarshalText(b []byte) error {
   203  	if t == nil {
   204  		return fmt.Errorf("nil pointer")
   205  	}
   206  	*t = Time(b)
   207  	return nil
   208  }
   209  
   210  // Binary handles base64Binary type
   211  type Binary []byte
   212  
   213  // MarshalJSON handles outputting json of base64Binary.  Empty value
   214  // outputs null, a nil ptr is omitted with omitempty.
   215  func (b Binary) MarshalJSON() ([]byte, error) {
   216  	if len(b) > 0 {
   217  		return json.Marshal([]byte(b))
   218  	}
   219  	return []byte("null"), nil
   220  }
   221  
   222  // UnmarshalJSON does null handling during json decode,
   223  func (b *Binary) UnmarshalJSON(buff []byte) error {
   224  	var bx []byte
   225  	if err := json.Unmarshal(buff, &bx); err != nil {
   226  		return err
   227  	}
   228  	*b = bx
   229  	return nil
   230  }
   231  
   232  // NullValue represents a sent or received null in json
   233  type NullValue struct{}
   234  
   235  // RecordMap is created during an Any.JSONUnmarshal when
   236  // the record type is not registered
   237  type RecordMap map[string]interface{}
   238  
   239  // SObjectName returns the attributes type value
   240  func (m RecordMap) SObjectName() string {
   241  	if m != nil {
   242  		switch ix := m["attributes"].(type) {
   243  		case map[string]interface{}:
   244  			nm, _ := ix["type"].(string)
   245  			return nm
   246  		case map[string]string:
   247  			return ix["type"]
   248  		case Attributes:
   249  			return ix.Type
   250  		case *Attributes:
   251  			return ix.Type
   252  		}
   253  	}
   254  	return ""
   255  }
   256  
   257  // WithAttr set the attributes value
   258  func (m RecordMap) WithAttr(ref string) SObject {
   259  	if m != nil {
   260  		m["attributes"] = &Attributes{
   261  			Type: m.SObjectName(),
   262  			Ref:  ref,
   263  		}
   264  	}
   265  	return m
   266  }
   267  
   268  // Any is used to unmarshal an SObject json for undetermined objects.
   269  type Any struct {
   270  	SObject
   271  }
   272  
   273  // UnmarshalJSON uses the attributes data to determine the SObject type
   274  // the registered structs.  SObject
   275  func (a *Any) UnmarshalJSON(b []byte) error {
   276  	dec := json.NewDecoder(bytes.NewReader(b))
   277  	var attr *Attributes
   278  	for {
   279  		t, err := dec.Token()
   280  		if err == nil {
   281  			nm, _ := t.(string)
   282  			if nm == "attributes" {
   283  				if err = dec.Decode(&attr); err != nil {
   284  					return fmt.Errorf("attributes decode %w", err)
   285  				}
   286  				break
   287  			}
   288  			continue
   289  		}
   290  		return fmt.Errorf("attributes not found %v", err)
   291  	}
   292  	sobjPtrVal := sobjCatalog.getNewValue(attr.Type)
   293  
   294  	if err := json.Unmarshal(b, sobjPtrVal.Interface()); err != nil {
   295  		return err
   296  	}
   297  	a.SObject = sobjPtrVal.Elem().Interface().(SObject)
   298  	return nil
   299  }
   300  
   301  var mapSObjectStructs = make(map[string]reflect.Type)
   302  
   303  var sobjCatalog = &catalog{sobjects: make(map[string]reflect.Type)}
   304  
   305  // RegisterSObjectTypes catalogs the type of the SObject structs.  The Any
   306  // type uses these registrations to unmarshal a salesforce response
   307  // into the appropriate type.
   308  func RegisterSObjectTypes(sobjs ...SObject) {
   309  	for _, o := range sobjs {
   310  		sobjCatalog.sobjects[o.SObjectName()] = reflect.TypeOf(o)
   311  	}
   312  }
   313  
   314  type catalog struct {
   315  	sobjects map[string]reflect.Type
   316  	m        sync.Mutex
   317  }
   318  
   319  func (c *catalog) getNewValue(name string) reflect.Value {
   320  	c.m.Lock()
   321  	defer c.m.Unlock()
   322  	ty, ok := c.sobjects[name]
   323  	if ok {
   324  		return reflect.New(ty)
   325  	}
   326  	var mapValues RecordMap
   327  	return reflect.New(reflect.TypeOf(mapValues))
   328  }
   329  
   330  // SObject is a struct used for Create, Update and Delete operations
   331  type SObject interface {
   332  	SObjectName() string
   333  	WithAttr(string) SObject
   334  }
   335  
   336  // Error is the error response for most calls
   337  type Error struct {
   338  	StatusCode string   `json:"statusCode,omitempty"`
   339  	Message    string   `json:"message,omitempty"`
   340  	Fields     []string `json:"fields,omitempty"`
   341  }
   342  
   343  // LogError used to report individual record errors
   344  type LogError struct {
   345  	Index      int     `json:"index"`
   346  	ExternalID string  `json:"external_id,omitempty"`
   347  	Errors     []Error `json:"errors,omitempty"`
   348  }