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 }