-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsalesforce.go
327 lines (274 loc) · 8.62 KB
/
salesforce.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
package salesforce
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/beeekind/go-salesforce-sdk/client"
"github.com/beeekind/go-salesforce-sdk/codegen"
"github.com/beeekind/go-salesforce-sdk/composite"
"github.com/beeekind/go-salesforce-sdk/metadata"
"github.com/beeekind/go-salesforce-sdk/requests"
"github.com/beeekind/go-salesforce-sdk/soql"
"github.com/beeekind/go-salesforce-sdk/types"
"github.com/beeekind/ratelimit"
"github.com/beeekind/ratelimit/memory"
)
// "If the data can't be found here, check out what's behind API endpoint number 5"
// DefaultClient ...
var DefaultClient = client.Must(
client.WithLoginFailover(
client.WithPasswordBearer(
os.Getenv("SALESFORCE_SDK_CLIENT_ID"),
os.Getenv("SALESFORCE_SDK_CLIENT_SECRET"),
os.Getenv("SALESFORCE_SDK_USERNAME"),
os.Getenv("SALESFORCE_SDK_PASSWORD"),
os.Getenv("SALESFORCE_SDK_SECURITY_TOKEN"),
),
client.WithJWTBearer(
os.Getenv("SALESFORCE_SDK_CLIENT_ID"),
os.Getenv("SALESFORCE_SDK_USERNAME"),
"../private.pem",
),
),
client.WithLimiter(ratelimit.New(5, time.Second, 5, memory.New())),
)
/**
[{"message":"The users password has expired, you must call SetPassword before attempting any other API operations","errorCode":"INVALID_OPERATION_WITH_EXPIRED_PASSWORD"}] SELECT QualifiedApiName, Description FROM EntityDefinition WHERE QualifiedApiName IN ('Account')
*/
// Versions returns data on available API versions for the Salesforce REST API
//
// This method is used during authentication so that we may default to the latest
// REST API endpoint.
func Versions() ([]*client.APIVersion, error) {
return DefaultClient.APIVersions()
}
// Services are api endpoints for RESTful operations within the Salesforce API
func Services() (services map[string]string, err error) {
_, err = requests.
Sender(DefaultClient).
URL("").
JSON(&services)
if err != nil {
return nil, err
}
// map values like "/services/data/v49.0/tooling" => "tooling"
for serviceName, serviceURL := range services {
parts := strings.Split(serviceURL, "/")
services[serviceName] = strings.Split(serviceURL, "/")[len(parts)-1]
}
return services, nil
}
// Types returns a golang type definition(s) for the JSON response of an endpoint
func Types(structName string, endpoint string) (codegen.Structs, error) {
response, err := requests.
Sender(DefaultClient).
URL(endpoint).
Response()
if err != nil {
return nil, err
}
contents, err := requests.ReadAndCloseResponse(response)
// we can still derive types from an error response for an endpoint, so as long as
// there is something in response.Body we will ignore this error. For example, disabled API features
// return errors and thats ok.
//
// parameterizedSearch 400 | serviceTemplates 401 | payments 404 |
// compactLayouts 400 | smartdatadiscovery 403 | prechatForms 405 |
// jsonxform 500
if err != nil {
if contents == nil || len(contents) == 0 {
return nil, fmt.Errorf("retrieving types for %s: %w", endpoint, err)
}
}
repr, err := codegen.FromJSON(structName, "", contents)
if err != nil {
return nil, err
}
return repr, nil
}
// SObjects returns the result of a request to the /sobjects endpoint
func SObjects() (results *metadata.Sobjects, err error) {
_, err = requests.
Sender(DefaultClient).
URL("sobjects").
JSON(&results)
if err != nil {
return nil, err
}
return results, nil
}
// Describe returns the description of a given Salesforce Object
func Describe(objectName string) (describe *metadata.Describe, err error) {
_, err = requests.
Sender(DefaultClient).
URL(fmt.Sprintf("%s/%s/%s", "sobjects", objectName, "describe")).
JSON(&describe)
if err != nil {
return nil, err
}
return describe, nil
}
// AllEntities ...
// "If the data can't be found here, check out what's behind API endpoint number 5"
// ~~ B. Bonnette
// DownloadFile returns the given file via its ContentVersion ID
func DownloadFile(contentVersionID string) ([]byte, error) {
response, err := requests.
Sender(DefaultClient).
URL(fmt.Sprintf("sobjects/ContentVersion/%s/VersionData", contentVersionID)).
Method(http.MethodGet).
Response()
if err != nil {
return nil, err
}
return requests.ReadAndCloseResponse(response)
}
// Attachment returns the given Attachment by ID
func Attachment(ID string) ([]byte, error) {
response, err := requests.
Sender(DefaultClient).
URL(fmt.Sprintf("sobjects/Attachment/%s/body", ID)).
Method(http.MethodGet).
Response()
if err != nil {
return nil, err
}
return requests.ReadAndCloseResponse(response)
}
// Document returns the given Document by ID
func Document(req requests.Builder, ID string) ([]byte, error) {
response, err := req.
Sender(DefaultClient).
URL(fmt.Sprintf("sobjects/Document/%s/body", ID)).
Method(http.MethodGet).
Response()
if err != nil {
return nil, err
}
return requests.ReadAndCloseResponse(response)
}
// Count for the given objectName "Lead" "Account" or "User"
func Count(objectName string) (int, error) {
var response types.QueryResponse
contents, err := requests.
Sender(DefaultClient).
URL("query").
SQLizer(soql.Select("count()").From(objectName)).
JSON(&response)
if err != nil {
println(string(contents))
println(err.Error())
return 0, err
}
return response.TotalSize, nil
}
// Find returns all paginated resources for a given query. If there
// are many results and/or many fields this method will take longer
// to execute and use more of your org's API limit.
//
// The parameter dst should be a pointer value to a slice of types matching
// the expected query records.
func Find(query string, dst interface{}) error {
return requests.
Sender(DefaultClient).
URL("query").
QueryMore(soql.String(query), dst, false)
}
// FindAll is akin to the queryAll resource which returns
// soft deleted resources in addition to regular records.
//
// The parameter dst should be a pointer value to a slice of types matching
// the expected query records.
func FindAll(query string, dst interface{}) error {
return requests.
Sender(DefaultClient).
URL("queryAll").
QueryMore(soql.String(query), dst, true)
}
// FindByID returns a single result filtered by Id.
//
// The parameter dst should be a pointer to a type matching the
// expected query record.
func FindByID(objectName string, objectID string, fields []string, dst interface{}) error {
var response types.QueryParts
_, err := requests.
Sender(DefaultClient).
URL(metadata.QueryEndpoint).
SQLizer(
soql.Select(fields...).
From(objectName).
Where(soql.Eq{"Id": objectID}),
).
JSON(&response)
if err != nil {
return err
}
if len(response.Records) != 1 {
return fmt.Errorf("resource.FindByID query did not return any records")
}
if err := json.Unmarshal(response.Records[0], dst); err != nil {
return err
}
return nil
}
// Create created the given objectName
//
// This SDK goes out of its way to not be an ORM which is why the method
// signature doesnt use a generated type like those derived from the codegen
// package.
func Create(objectName string, fields map[string]interface{}) (ID string, err error) {
var response composite.Output
contents, err := requests.
Sender(DefaultClient).
URL(fmt.Sprintf("%s/%s", metadata.SobjectsEndpoint, objectName)).
Method(http.MethodPost).
Header("Content-Type", "application/json").
Marshal(fields).
JSON(&response)
if err != nil {
var errors composite.Errors
if err = json.Unmarshal(contents, &errors); err != nil {
return "", err
}
return "", errors
}
//if len(response.Errors) > 0 {
// return "", response.Errors
//}
return response.ID, nil
}
// UpdateByID updates the given objectName with the given ID.
//
// Salesforce update responses return empty response bodies and statusCode
// 204 upon success.
func UpdateByID(objectName string, ID string, fields map[string]interface{}) error {
var result composite.Error
_, err := requests.
Sender(DefaultClient).
URL(fmt.Sprintf("%s/%s/%s", metadata.SobjectsEndpoint, objectName, ID)).
Method(http.MethodPatch).
Header("Content-Type", "application/json").
Marshal(fields).
JSON(&result)
if err != nil && !errors.Is(err, requests.ErrUnmarshalEmpty) {
return fmt.Errorf("deleting object %s: %s: %w", objectName, ID, err)
}
return nil
}
// DeleteByID deletes the given objectName with the given ID
func DeleteByID(objectName string, ID string) error {
var result composite.Error
_, err := requests.
Sender(DefaultClient).
URL(fmt.Sprintf("%s/%s/%s", metadata.SobjectsEndpoint, objectName, ID)).
Method(http.MethodDelete).
JSON(&result)
if err != nil && !errors.Is(err, requests.ErrUnmarshalEmpty) {
return fmt.Errorf("crud.DeleteByID %s: %w", ID, err)
}
return nil
}