restforce 5.0.6 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +9 -9
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +22 -0
- data/Gemfile +10 -6
- data/README.md +53 -4
- data/lib/restforce/abstract_client.rb +1 -0
- data/lib/restforce/collection.rb +15 -1
- data/lib/restforce/concerns/api.rb +1 -1
- data/lib/restforce/concerns/composite_api.rb +104 -0
- data/lib/restforce/concerns/picklists.rb +1 -1
- data/lib/restforce/error_code.rb +24 -9
- data/lib/restforce/version.rb +1 -1
- data/lib/restforce.rb +2 -0
- data/restforce.gemspec +4 -13
- data/spec/integration/abstract_client_spec.rb +21 -24
- data/spec/integration/data/client_spec.rb +6 -2
- data/spec/spec_helper.rb +10 -0
- data/spec/support/client_integration.rb +7 -7
- data/spec/support/concerns.rb +1 -1
- data/spec/support/middleware.rb +1 -2
- data/spec/unit/collection_spec.rb +4 -4
- data/spec/unit/concerns/api_spec.rb +11 -11
- data/spec/unit/concerns/authentication_spec.rb +6 -6
- data/spec/unit/concerns/composite_api_spec.rb +143 -0
- data/spec/unit/concerns/streaming_spec.rb +4 -4
- data/spec/unit/config_spec.rb +1 -1
- data/spec/unit/middleware/authentication/jwt_bearer_spec.rb +4 -4
- data/spec/unit/middleware/authentication/password_spec.rb +2 -2
- data/spec/unit/middleware/authentication/token_spec.rb +2 -2
- data/spec/unit/middleware/gzip_spec.rb +2 -2
- data/spec/unit/middleware/raise_error_spec.rb +20 -10
- metadata +14 -94
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 891d81fbf377945b09db6058467d21da1720d985e70ad060e4bcbe35d5efc9f6
|
|
4
|
+
data.tar.gz: 2ce207d94d49504e28680d7b854633cc3c2c0b329ed23b04769528799b71785e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04e9f867ebadf6a8d61ffad3ebdb14b9924011df9b408efb9f17bb8a65c7ad5a4de1caa18b78dd365b889bf6b32801d7c30d7de02f105be38a3b4111cf83b792
|
|
7
|
+
data.tar.gz: c72397f42ef4a4f95a053557bf8971e3eeff78994b21ccdc232638ef1847c08e7e1c8e9e33f6055725c9fbc57755de85d474cf6063094ec6e33e211a93ba14d4
|
data/.circleci/config.yml
CHANGED
|
@@ -34,23 +34,23 @@ references:
|
|
|
34
34
|
destination: test-results
|
|
35
35
|
|
|
36
36
|
jobs:
|
|
37
|
-
build-
|
|
37
|
+
build-ruby30:
|
|
38
38
|
docker:
|
|
39
|
-
- image: circleci/ruby:
|
|
39
|
+
- image: circleci/ruby:3.0
|
|
40
40
|
steps: *steps
|
|
41
|
-
build-
|
|
41
|
+
build-ruby27:
|
|
42
42
|
docker:
|
|
43
|
-
- image: circleci/ruby:2.
|
|
43
|
+
- image: circleci/ruby:2.7
|
|
44
44
|
steps: *steps
|
|
45
|
-
build-
|
|
45
|
+
build-ruby26:
|
|
46
46
|
docker:
|
|
47
|
-
- image: circleci/ruby:2.
|
|
47
|
+
- image: circleci/ruby:2.6
|
|
48
48
|
steps: *steps
|
|
49
49
|
|
|
50
50
|
workflows:
|
|
51
51
|
version: 2
|
|
52
52
|
tests:
|
|
53
53
|
jobs:
|
|
54
|
-
- build-
|
|
55
|
-
- build-
|
|
56
|
-
- build-
|
|
54
|
+
- build-ruby30
|
|
55
|
+
- build-ruby27
|
|
56
|
+
- build-ruby26
|
data/.rubocop.yml
CHANGED
|
@@ -8,7 +8,7 @@ AllCops:
|
|
|
8
8
|
- .*/**/*
|
|
9
9
|
- vendor/**/*
|
|
10
10
|
NewCops: enable
|
|
11
|
-
TargetRubyVersion: 2.
|
|
11
|
+
TargetRubyVersion: 2.6
|
|
12
12
|
|
|
13
13
|
# Limit lines to 90 characters.
|
|
14
14
|
Layout/LineLength:
|
|
@@ -71,4 +71,4 @@ Naming/FileName:
|
|
|
71
71
|
- Guardfile
|
|
72
72
|
|
|
73
73
|
Lint/UriEscapeUnescape:
|
|
74
|
-
Enabled: false
|
|
74
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
# 5.2.1 (Dec 8, 2021)
|
|
2
|
+
|
|
3
|
+
* Handle the `OPERATION_TOO_LARGE` error returned by Salesforce (@timrogers)
|
|
4
|
+
* Handle the `INVALID_SIGNUP_COUNTRY` error returned by Salesforce (@timrogers)
|
|
5
|
+
|
|
6
|
+
## 5.2.0 (Oct 15, 2021)
|
|
7
|
+
|
|
8
|
+
* Add support for Salesforce's Composite API and Composite Batch API (@meenie, @amacdougall)
|
|
9
|
+
* Improve the performance of counting numbers of query results with `Restforce::Collection#count`, avoiding unnecessary API requests (@jhass)
|
|
10
|
+
|
|
11
|
+
## 5.1.1 (Oct 13, 2021)
|
|
12
|
+
|
|
13
|
+
* Handle the `INVALID_REPLICATION_DATE` error returned by Salesforce (@michaelwnyc)
|
|
14
|
+
* Handle the `BIG_OBJECT_UNSUPPORTED_OPERATION` error returned by Salesforce (@remon)
|
|
15
|
+
|
|
16
|
+
## 5.1.0 (Aug 26, 2021)
|
|
17
|
+
|
|
18
|
+
* Add official support for Ruby 3.0 (@timrogers)
|
|
19
|
+
* Drop support for Ruby 2.5, which has reached end-of-life (@timrogers)
|
|
20
|
+
* Handle the `QUERY_TIMEOUT` error returned by Salesforce (@timrogers)
|
|
21
|
+
* Remove unnecessary development dependencies for the gem, which can just be in the project's `Gemfile` (@timrogers)
|
|
22
|
+
|
|
1
23
|
## 5.0.6 (Jun 17, 2021)
|
|
2
24
|
|
|
3
25
|
* Handle the `API_DISABLED_FOR_ORG` error returned by Salesforce (@cmac)
|
data/Gemfile
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
source 'https://rubygems.org'
|
|
4
4
|
gemspec
|
|
5
5
|
|
|
6
|
+
gem 'faye' unless RUBY_PLATFORM == 'java'
|
|
7
|
+
gem 'guard-rspec'
|
|
8
|
+
gem 'guard-rubocop'
|
|
6
9
|
gem 'jruby-openssl', platforms: :jruby
|
|
7
|
-
gem 'jwt'
|
|
8
10
|
gem 'rake'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
gem 'rspec', '~> 3.10.0'
|
|
12
|
+
gem 'rspec-collection_matchers', '~> 1.2.0'
|
|
13
|
+
gem 'rspec-its', '~> 1.3.0'
|
|
14
|
+
gem 'rspec_junit_formatter', '~> 0.4.1'
|
|
15
|
+
gem 'rubocop', '~> 1.23.0'
|
|
16
|
+
gem 'simplecov', '~> 0.21.2'
|
|
17
|
+
gem 'webmock', '~> 3.14.0'
|
data/README.md
CHANGED
|
@@ -12,6 +12,8 @@ Features include:
|
|
|
12
12
|
* Support for parent-to-child relationships.
|
|
13
13
|
* Support for aggregate queries.
|
|
14
14
|
* Support for the [Streaming API](#streaming)
|
|
15
|
+
* Support for the [Composite API](#composite-api)
|
|
16
|
+
* Support for the [Composite Batch API](#composite-batch-api)
|
|
15
17
|
* Support for the GetUpdated API
|
|
16
18
|
* Support for blob data types.
|
|
17
19
|
* Support for GZIP compression.
|
|
@@ -25,7 +27,7 @@ Features include:
|
|
|
25
27
|
|
|
26
28
|
Add this line to your application's Gemfile:
|
|
27
29
|
|
|
28
|
-
gem 'restforce', '~> 5.
|
|
30
|
+
gem 'restforce', '~> 5.2.1'
|
|
29
31
|
|
|
30
32
|
And then execute:
|
|
31
33
|
|
|
@@ -35,8 +37,9 @@ Or install it yourself as:
|
|
|
35
37
|
|
|
36
38
|
$ gem install restforce
|
|
37
39
|
|
|
38
|
-
__As of version 5.
|
|
40
|
+
__As of version 5.1.0, this gem is only compatible with Ruby 2.6.0 and later.__ If you're using an earlier Ruby version:
|
|
39
41
|
|
|
42
|
+
* for Ruby 2.5, use version 5.0.6 or earlier
|
|
40
43
|
* for Ruby 2.4, use version 4.3.0 or earlier
|
|
41
44
|
* for Ruby 2.3, use version 3.2.0 or earlier
|
|
42
45
|
* for Ruby versions 2.2, 2.1 and 2.0, use version 2.5.3 or earlier
|
|
@@ -459,7 +462,7 @@ info.user_id
|
|
|
459
462
|
|
|
460
463
|
### File Uploads
|
|
461
464
|
|
|
462
|
-
Using the new [Blob Data](
|
|
465
|
+
Using the new [Blob Data](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm) api feature (500mb limit):
|
|
463
466
|
|
|
464
467
|
```ruby
|
|
465
468
|
client.create('Document', FolderId: '00lE0000000FJ6H',
|
|
@@ -477,7 +480,7 @@ client.create('Document', FolderId: '00lE0000000FJ6H',
|
|
|
477
480
|
Body: Base64::encode64(File.read('image.jpg'))
|
|
478
481
|
```
|
|
479
482
|
|
|
480
|
-
_See also: [Inserting or updating blob data](
|
|
483
|
+
_See also: [Inserting or updating blob data](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_insert_update_blob.htm)_
|
|
481
484
|
|
|
482
485
|
* * *
|
|
483
486
|
|
|
@@ -572,6 +575,52 @@ end
|
|
|
572
575
|
Boom, you're now receiving push notifications when Accounts are
|
|
573
576
|
created/updated.
|
|
574
577
|
|
|
578
|
+
#### Composite API
|
|
579
|
+
|
|
580
|
+
Restforce supports the [Composite API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_composite.htm).
|
|
581
|
+
This feature permits the user to send a composite object—that is, a complex
|
|
582
|
+
object with nested children—in a single API call. Up to 25 requests may be
|
|
583
|
+
included in a single composite.
|
|
584
|
+
|
|
585
|
+
Note that `GET` is not yet implemented for this API.
|
|
586
|
+
|
|
587
|
+
```ruby
|
|
588
|
+
# build up an array of requests:
|
|
589
|
+
requests << {
|
|
590
|
+
method: :update,
|
|
591
|
+
sobject: sobject, # e.g. "Contact"
|
|
592
|
+
reference_id: reference_id,
|
|
593
|
+
data: data
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
# send every 25 requests as a subrequest in a single composite call
|
|
597
|
+
requests.each_slice(25).map do |req_slice|
|
|
598
|
+
client.composite do |subrequest|
|
|
599
|
+
req_slice.each do |r|
|
|
600
|
+
subrequest.send *r.values
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# note that we're using `map` to return an array of each responses to each
|
|
606
|
+
# composite call; 100 requests will produce 4 responses
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
#### Composite Batch API
|
|
610
|
+
|
|
611
|
+
Restforce supports the [Composite Batch API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_batch.htm).
|
|
612
|
+
This feature permits up to 25 subrequests in a single request, though each
|
|
613
|
+
subrequest counts against the API limit. On the other hand, it has fewer
|
|
614
|
+
limitations than the Composite API.
|
|
615
|
+
|
|
616
|
+
```
|
|
617
|
+
client.batch do |subrequests|
|
|
618
|
+
subrequests.create('Object', name: 'test')
|
|
619
|
+
subrequests.update('Object', id: '123', name: 'test')
|
|
620
|
+
subrequests.destroy('Object', '123')
|
|
621
|
+
end
|
|
622
|
+
```
|
|
623
|
+
|
|
575
624
|
#### Replaying Events
|
|
576
625
|
|
|
577
626
|
Since API version 37.0, Salesforce stores events for 24 hours and they can be
|
data/lib/restforce/collection.rb
CHANGED
|
@@ -27,12 +27,26 @@ module Restforce
|
|
|
27
27
|
@raw_page['records'].size
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
# Return the
|
|
30
|
+
# Return the number of items in the Collection without making any additional
|
|
31
|
+
# requests and going through all of the pages of results, one by one. Instead,
|
|
32
|
+
# we can rely on the total count of results which Salesforce returns.
|
|
31
33
|
def size
|
|
32
34
|
@raw_page['totalSize']
|
|
33
35
|
end
|
|
34
36
|
alias length size
|
|
35
37
|
|
|
38
|
+
def count(*args)
|
|
39
|
+
# By default, `Enumerable`'s `#count` uses `#each`, which means going through all
|
|
40
|
+
# of the pages of results, one by one. Instead, we can use `#size` which we have
|
|
41
|
+
# already overridden to work in a smarter, more efficient way. This only works for
|
|
42
|
+
# the simple version of `#count` with no arguments. When called with an argument or
|
|
43
|
+
# a block, you need to know what the items in the collection actually are, so we
|
|
44
|
+
# call `super` and end up iterating through each item in the collection.
|
|
45
|
+
return size unless block_given? || !args.empty?
|
|
46
|
+
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
36
50
|
# Returns true if the size of the Collection is zero.
|
|
37
51
|
def empty?
|
|
38
52
|
size.zero?
|
|
@@ -380,7 +380,7 @@ module Restforce
|
|
|
380
380
|
end
|
|
381
381
|
else
|
|
382
382
|
api_patch "sobjects/#{sobject}/#{field}/" \
|
|
383
|
-
|
|
383
|
+
"#{ERB::Util.url_encode(external_id)}", attrs
|
|
384
384
|
end
|
|
385
385
|
|
|
386
386
|
response.body.respond_to?(:fetch) ? response.body.fetch('id', true) : true
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'restforce/concerns/verbs'
|
|
4
|
+
|
|
5
|
+
module Restforce
|
|
6
|
+
module Concerns
|
|
7
|
+
module CompositeAPI
|
|
8
|
+
extend Restforce::Concerns::Verbs
|
|
9
|
+
|
|
10
|
+
define_verbs :post
|
|
11
|
+
|
|
12
|
+
def composite(all_or_none: false, collate_subrequests: false)
|
|
13
|
+
subrequests = Subrequests.new(options)
|
|
14
|
+
yield(subrequests)
|
|
15
|
+
|
|
16
|
+
if subrequests.requests.length > 25
|
|
17
|
+
raise ArgumentError, 'Cannot have more than 25 subrequests.'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
properties = {
|
|
21
|
+
compositeRequest: subrequests.requests,
|
|
22
|
+
allOrNone: all_or_none,
|
|
23
|
+
collateSubrequests: collate_subrequests
|
|
24
|
+
}
|
|
25
|
+
response = api_post('composite', properties.to_json)
|
|
26
|
+
|
|
27
|
+
results = response.body['CompositeResponse']
|
|
28
|
+
has_errors = results.any? { |result| result['HttpStatusCode'].digits.last == 4 }
|
|
29
|
+
if all_or_none && has_errors
|
|
30
|
+
last_error_index = results.rindex { |result| result['HttpStatusCode'] != 412 }
|
|
31
|
+
last_error = results[last_error_index]
|
|
32
|
+
raise CompositeAPIError, last_error['Body'][0]['errorCode']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
results
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def composite!(collate_subrequests: false, &block)
|
|
39
|
+
composite(all_or_none: true, collate_subrequests: collate_subrequests, &block)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Subrequests
|
|
43
|
+
def initialize(options)
|
|
44
|
+
@options = options
|
|
45
|
+
@requests = []
|
|
46
|
+
end
|
|
47
|
+
attr_reader :options, :requests
|
|
48
|
+
|
|
49
|
+
def create(sobject, reference_id, attrs)
|
|
50
|
+
requests << {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
url: composite_api_path(sobject),
|
|
53
|
+
body: attrs,
|
|
54
|
+
referenceId: reference_id
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def update(sobject, reference_id, attrs)
|
|
59
|
+
id = attrs.fetch(attrs.keys.find { |k, _v| k.to_s.casecmp?('id') }, nil)
|
|
60
|
+
raise ArgumentError, 'Id field missing from attrs.' unless id
|
|
61
|
+
|
|
62
|
+
attrs_without_id = attrs.reject { |k, _v| k.to_s.casecmp?('id') }
|
|
63
|
+
requests << {
|
|
64
|
+
method: 'PATCH',
|
|
65
|
+
url: composite_api_path("#{sobject}/#{id}"),
|
|
66
|
+
body: attrs_without_id,
|
|
67
|
+
referenceId: reference_id
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def destroy(sobject, reference_id, id)
|
|
72
|
+
requests << {
|
|
73
|
+
method: 'DELETE',
|
|
74
|
+
url: composite_api_path("#{sobject}/#{id}"),
|
|
75
|
+
referenceId: reference_id
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def upsert(sobject, reference_id, ext_field, attrs)
|
|
80
|
+
raise ArgumentError, 'External id field missing.' unless ext_field
|
|
81
|
+
|
|
82
|
+
ext_id = attrs.fetch(attrs.keys.find do |k, _v|
|
|
83
|
+
k.to_s.casecmp?(ext_field.to_s)
|
|
84
|
+
end, nil)
|
|
85
|
+
raise ArgumentError, 'External id missing from attrs.' unless ext_id
|
|
86
|
+
|
|
87
|
+
attrs_without_ext_id = attrs.reject { |k, _v| k.to_s.casecmp?(ext_field) }
|
|
88
|
+
requests << {
|
|
89
|
+
method: 'PATCH',
|
|
90
|
+
url: composite_api_path("#{sobject}/#{ext_field}/#{ext_id}"),
|
|
91
|
+
body: attrs_without_ext_id,
|
|
92
|
+
referenceId: reference_id
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def composite_api_path(path)
|
|
99
|
+
"/services/data/v#{options[:api_version]}/sobjects/#{path}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -85,7 +85,7 @@ module Restforce
|
|
|
85
85
|
def valid?(picklist_entry)
|
|
86
86
|
valid_for = picklist_entry['validFor'].ljust(16, 'A').unpack1('m').
|
|
87
87
|
unpack('C*')
|
|
88
|
-
(valid_for[index >> 3] & (0x80 >> index % 8)).positive?
|
|
88
|
+
(valid_for[index >> 3] & (0x80 >> (index % 8))).positive?
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
end
|
data/lib/restforce/error_code.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Restforce
|
|
4
4
|
module ErrorCode
|
|
5
5
|
GITHUB_ISSUE_URL = "https://github.com/restforce/restforce/issues/new?template=" \
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
"unhandled-salesforce-error.md&title=Unhandled+Salesforce+error" \
|
|
7
|
+
"%3A+%3Cinsert+error+code+here%3E"
|
|
8
8
|
|
|
9
9
|
# We define all of the known errors returned by Salesforce based on the
|
|
10
10
|
# documentation at
|
|
@@ -31,6 +31,8 @@ module Restforce
|
|
|
31
31
|
|
|
32
32
|
class BccSelfNotAllowedIfBccComplianceEnabled < ResponseError; end
|
|
33
33
|
|
|
34
|
+
class BigObjectUnsupportedOperation < ResponseError; end
|
|
35
|
+
|
|
34
36
|
class CannotCascadeProductActive < ResponseError; end
|
|
35
37
|
|
|
36
38
|
class CannotChangeFieldTypeOfApexReferencedField < ResponseError; end
|
|
@@ -237,10 +239,14 @@ module Restforce
|
|
|
237
239
|
|
|
238
240
|
class InvalidReadOnlyUserDml < ResponseError; end
|
|
239
241
|
|
|
242
|
+
class InvalidReplicationDate < ResponseError; end
|
|
243
|
+
|
|
240
244
|
class InvalidSaveAsActivityFlag < ResponseError; end
|
|
241
245
|
|
|
242
246
|
class InvalidSessionId < ResponseError; end
|
|
243
247
|
|
|
248
|
+
class InvalidSignupCountry < ResponseError; end
|
|
249
|
+
|
|
244
250
|
class InvalidStatus < ResponseError; end
|
|
245
251
|
|
|
246
252
|
class InvalidType < ResponseError; end
|
|
@@ -327,6 +333,8 @@ module Restforce
|
|
|
327
333
|
|
|
328
334
|
class OpWithInvalidUserTypeException < ResponseError; end
|
|
329
335
|
|
|
336
|
+
class OperationTooLarge < ResponseError; end
|
|
337
|
+
|
|
330
338
|
class OptedOutOfMassMail < ResponseError; end
|
|
331
339
|
|
|
332
340
|
class PackageLicenseRequired < ResponseError; end
|
|
@@ -341,6 +349,8 @@ module Restforce
|
|
|
341
349
|
|
|
342
350
|
class PrivateContactOnAsset < ResponseError; end
|
|
343
351
|
|
|
352
|
+
class QueryTimeout < ResponseError; end
|
|
353
|
+
|
|
344
354
|
class RecordInUseByWorkflow < ResponseError; end
|
|
345
355
|
|
|
346
356
|
class RequestLimitExceeded < ResponseError; end
|
|
@@ -411,6 +421,7 @@ module Restforce
|
|
|
411
421
|
BccNotAllowedIfBccComplianceEnabled,
|
|
412
422
|
"BCC_SELF_NOT_ALLOWED_IF_BCC_COMPLIANCE_ENABLED" =>
|
|
413
423
|
BccSelfNotAllowedIfBccComplianceEnabled,
|
|
424
|
+
"BIG_OBJECT_UNSUPPORTED_OPERATION" => BigObjectUnsupportedOperation,
|
|
414
425
|
"CANNOT_CASCADE_PRODUCT_ACTIVE" => CannotCascadeProductActive,
|
|
415
426
|
"CANNOT_CHANGE_FIELD_TYPE_OF_APEX_REFERENCED_FIELD" =>
|
|
416
427
|
CannotChangeFieldTypeOfApexReferencedField,
|
|
@@ -521,8 +532,10 @@ module Restforce
|
|
|
521
532
|
"INVALID_PARTNER_NETWORK_STATUS" => InvalidPartnerNetworkStatus,
|
|
522
533
|
"INVALID_PERSON_ACCOUNT_OPERATION" => InvalidPersonAccountOperation,
|
|
523
534
|
"INVALID_READ_ONLY_USER_DML" => InvalidReadOnlyUserDml,
|
|
535
|
+
"INVALID_REPLICATION_DATE" => InvalidReplicationDate,
|
|
524
536
|
"INVALID_SAVE_AS_ACTIVITY_FLAG" => InvalidSaveAsActivityFlag,
|
|
525
537
|
"INVALID_SESSION_ID" => InvalidSessionId,
|
|
538
|
+
"INVALID_SIGNUP_COUNTRY" => InvalidSignupCountry,
|
|
526
539
|
"INVALID_STATUS" => InvalidStatus,
|
|
527
540
|
"INVALID_TYPE" => InvalidType,
|
|
528
541
|
"INVALID_TYPE_FOR_OPERATION" => InvalidTypeForOperation,
|
|
@@ -566,6 +579,7 @@ module Restforce
|
|
|
566
579
|
"NUMBER_OUTSIDE_VALID_RANGE" => NumberOutsideValidRange,
|
|
567
580
|
"NUM_HISTORY_FIELDS_BY_SOBJECT_EXCEEDED" => NumHistoryFieldsBySobjectExceeded,
|
|
568
581
|
"OP_WITH_INVALID_USER_TYPE_EXCEPTION" => OpWithInvalidUserTypeException,
|
|
582
|
+
"OPERATION_TOO_LARGE" => OperationTooLarge,
|
|
569
583
|
"OPTED_OUT_OF_MASS_MAIL" => OptedOutOfMassMail,
|
|
570
584
|
"PACKAGE_LICENSE_REQUIRED" => PackageLicenseRequired,
|
|
571
585
|
"PLATFORM_EVENT_ENCRYPTION_ERROR" => PlatformEventEncryptionError,
|
|
@@ -573,6 +587,7 @@ module Restforce
|
|
|
573
587
|
"PLATFORM_EVENT_PUBLISH_FAILED" => PlatformEventPublishFailed,
|
|
574
588
|
"PORTAL_USER_ALREADY_EXISTS_FOR_CONTACT" => PortalUserAlreadyExistsForContact,
|
|
575
589
|
"PRIVATE_CONTACT_ON_ASSET" => PrivateContactOnAsset,
|
|
590
|
+
"QUERY_TIMEOUT" => QueryTimeout,
|
|
576
591
|
"RECORD_IN_USE_BY_WORKFLOW" => RecordInUseByWorkflow,
|
|
577
592
|
"REQUEST_LIMIT_EXCEEDED" => RequestLimitExceeded,
|
|
578
593
|
"REQUEST_RUNNING_TOO_LONG" => RequestRunningTooLong,
|
|
@@ -606,9 +621,9 @@ module Restforce
|
|
|
606
621
|
def self.get_exception_class(error_code)
|
|
607
622
|
ERROR_EXCEPTION_CLASSES.fetch(error_code) do |_|
|
|
608
623
|
warn "[restforce] An unrecognised error code, `#{error_code}` has been " \
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
624
|
+
"received from Salesforce. Instead of raising an error-specific exception" \
|
|
625
|
+
", we'll raise a generic `ResponseError`. Please report this missing " \
|
|
626
|
+
"error code on GitHub at <#{GITHUB_ISSUE_URL}>."
|
|
612
627
|
|
|
613
628
|
# If we've received an unexpected error where we don't have a specific
|
|
614
629
|
# class defined, we can return a generic ResponseError instead
|
|
@@ -618,10 +633,10 @@ module Restforce
|
|
|
618
633
|
|
|
619
634
|
def self.const_missing(constant_name)
|
|
620
635
|
warn "[restforce] You're referring to a Restforce error that isn't defined, " \
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
636
|
+
"`#{name}::#{constant_name}` (for example by trying to `rescue` it). This " \
|
|
637
|
+
"might be our fault - we've recently made some changes to how errors are " \
|
|
638
|
+
"defined. If you're sure that this is a valid Salesforce error, then " \
|
|
639
|
+
"please create an issue on GitHub at <#{GITHUB_ISSUE_URL}>."
|
|
625
640
|
|
|
626
641
|
super(constant_name)
|
|
627
642
|
end
|
data/lib/restforce/version.rb
CHANGED
data/lib/restforce.rb
CHANGED
|
@@ -32,6 +32,7 @@ module Restforce
|
|
|
32
32
|
autoload :Base, 'restforce/concerns/base'
|
|
33
33
|
autoload :API, 'restforce/concerns/api'
|
|
34
34
|
autoload :BatchAPI, 'restforce/concerns/batch_api'
|
|
35
|
+
autoload :CompositeAPI, 'restforce/concerns/composite_api'
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
module Data
|
|
@@ -48,6 +49,7 @@ module Restforce
|
|
|
48
49
|
UnauthorizedError = Class.new(Faraday::ClientError)
|
|
49
50
|
APIVersionError = Class.new(Error)
|
|
50
51
|
BatchAPIError = Class.new(Error)
|
|
52
|
+
CompositeAPIError = Class.new(Error)
|
|
51
53
|
|
|
52
54
|
# Inherit from Faraday::ResourceNotFound for backwards-compatibility
|
|
53
55
|
# Consumers of this library that rescue and handle Faraday::ResourceNotFound
|
data/restforce.gemspec
CHANGED
|
@@ -19,23 +19,14 @@ Gem::Specification.new do |gem|
|
|
|
19
19
|
|
|
20
20
|
gem.metadata = {
|
|
21
21
|
'source_code_uri' => 'https://github.com/restforce/restforce',
|
|
22
|
-
'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md'
|
|
22
|
+
'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md',
|
|
23
|
+
'rubygems_mfa_required' => 'true'
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
gem.required_ruby_version = '>= 2.
|
|
26
|
+
gem.required_ruby_version = '>= 2.6'
|
|
26
27
|
|
|
27
28
|
gem.add_dependency 'faraday', '<= 2.0', '>= 0.9.0'
|
|
28
29
|
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 2.0']
|
|
29
|
-
|
|
30
|
+
gem.add_dependency 'hashie', '>= 1.2.0', '< 6.0'
|
|
30
31
|
gem.add_dependency 'jwt', ['>= 1.5.6']
|
|
31
|
-
|
|
32
|
-
gem.add_dependency 'hashie', '>= 1.2.0', '< 5.0'
|
|
33
|
-
|
|
34
|
-
gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
|
|
35
|
-
gem.add_development_dependency 'rspec', '~> 2.14.0'
|
|
36
|
-
gem.add_development_dependency 'rspec_junit_formatter', '~> 0.4.1'
|
|
37
|
-
|
|
38
|
-
gem.add_development_dependency 'rubocop', '~> 1.17.0'
|
|
39
|
-
gem.add_development_dependency 'simplecov', '~> 0.21.2'
|
|
40
|
-
gem.add_development_dependency 'webmock', '~> 3.13.0'
|
|
41
32
|
end
|
|
@@ -129,18 +129,15 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
129
129
|
JSON.parse(fixture('sobject/delete_error_response'))
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
end
|
|
132
|
+
it "raises Faraday::ResourceNotFound" do
|
|
133
|
+
expect { client.update!('Account', Id: '001D000000INjVe', Name: 'Foobar') }.
|
|
134
|
+
to raise_error do |exception|
|
|
135
|
+
expect(exception).to be_a(Faraday::ResourceNotFound)
|
|
137
136
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
}
|
|
137
|
+
expect(exception.message).
|
|
138
|
+
to start_with("#{error.first['errorCode']}: #{error.first['message']}")
|
|
139
|
+
end
|
|
140
|
+
end
|
|
144
141
|
end
|
|
145
142
|
end
|
|
146
143
|
|
|
@@ -158,7 +155,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
158
155
|
fixture: 'sobject/delete_error_response'
|
|
159
156
|
|
|
160
157
|
subject { client.update('Account', Id: '001D000000INjVe', Name: 'Foobar') }
|
|
161
|
-
it { should
|
|
158
|
+
it { should be false }
|
|
162
159
|
end
|
|
163
160
|
|
|
164
161
|
context 'with success' do
|
|
@@ -172,7 +169,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
172
169
|
client.update('Account', key => '001D000000INjVe', :Name => 'Foobar')
|
|
173
170
|
end
|
|
174
171
|
|
|
175
|
-
it { should
|
|
172
|
+
it { should be true }
|
|
176
173
|
end
|
|
177
174
|
end
|
|
178
175
|
end
|
|
@@ -191,7 +188,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
191
188
|
Name: 'Foobar')
|
|
192
189
|
end
|
|
193
190
|
|
|
194
|
-
it { should
|
|
191
|
+
it { should be true }
|
|
195
192
|
end
|
|
196
193
|
|
|
197
194
|
context 'with string external Id key' do
|
|
@@ -200,7 +197,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
200
197
|
'Name' => 'Foobar')
|
|
201
198
|
end
|
|
202
199
|
|
|
203
|
-
it { should
|
|
200
|
+
it { should be true }
|
|
204
201
|
end
|
|
205
202
|
end
|
|
206
203
|
|
|
@@ -257,14 +254,14 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
257
254
|
context 'with success' do
|
|
258
255
|
requests 'sobjects/Account/001D000000INjVe', method: :delete
|
|
259
256
|
|
|
260
|
-
it { should
|
|
257
|
+
it { should be true }
|
|
261
258
|
end
|
|
262
259
|
|
|
263
260
|
context 'with a space in the id' do
|
|
264
261
|
subject(:destroy!) { client.destroy!('Account', '001D000000 INjVe') }
|
|
265
262
|
requests 'sobjects/Account/001D000000%20INjVe', method: :delete
|
|
266
263
|
|
|
267
|
-
it { should
|
|
264
|
+
it { should be true }
|
|
268
265
|
end
|
|
269
266
|
end
|
|
270
267
|
|
|
@@ -277,13 +274,13 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
277
274
|
method: :delete,
|
|
278
275
|
status: 404
|
|
279
276
|
|
|
280
|
-
it { should
|
|
277
|
+
it { should be false }
|
|
281
278
|
end
|
|
282
279
|
|
|
283
280
|
context 'with success' do
|
|
284
281
|
requests 'sobjects/Account/001D000000INjVe', method: :delete
|
|
285
282
|
|
|
286
|
-
it { should
|
|
283
|
+
it { should be true }
|
|
287
284
|
end
|
|
288
285
|
end
|
|
289
286
|
|
|
@@ -368,8 +365,8 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
368
365
|
before do
|
|
369
366
|
@request = stub_login_request(
|
|
370
367
|
with_body: "grant_type=password&client_id=client_id" \
|
|
371
|
-
|
|
372
|
-
|
|
368
|
+
"&client_secret=client_secret&username=foo" \
|
|
369
|
+
"&password=barsecurity_token"
|
|
373
370
|
).to_return(status: 200, body: fixture(:auth_success_response))
|
|
374
371
|
end
|
|
375
372
|
|
|
@@ -420,8 +417,8 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
420
417
|
|
|
421
418
|
@query_request = stub_login_request(
|
|
422
419
|
with_body: "grant_type=password&client_id=client_id" \
|
|
423
|
-
|
|
424
|
-
|
|
420
|
+
"&client_secret=client_secret&username=foo&" \
|
|
421
|
+
"password=barsecurity_token"
|
|
425
422
|
).to_return(status: 200, body: fixture(:auth_success_response))
|
|
426
423
|
end
|
|
427
424
|
|
|
@@ -445,7 +442,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
445
442
|
|
|
446
443
|
@login = stub_login_request(
|
|
447
444
|
with_body: "grant_type=password&client_id=client_id&client_secret=" \
|
|
448
|
-
|
|
445
|
+
"client_secret&username=foo&password=barsecurity_token"
|
|
449
446
|
).to_return(status: 200, body: fixture(:auth_success_response))
|
|
450
447
|
end
|
|
451
448
|
|