restforce 4.2.1 → 5.2.4
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 +14 -9
- data/.github/ISSUE_TEMPLATE/unhandled-salesforce-error.md +17 -0
- data/.github/dependabot.yml +19 -0
- data/.rubocop.yml +5 -4
- data/CHANGELOG.md +96 -0
- data/CONTRIBUTING.md +21 -1
- data/Dockerfile +31 -0
- data/Gemfile +10 -7
- data/README.md +94 -21
- data/UPGRADING.md +38 -0
- data/docker-compose.yml +7 -0
- data/lib/restforce/abstract_client.rb +1 -0
- data/lib/restforce/collection.rb +27 -4
- data/lib/restforce/concerns/api.rb +2 -2
- data/lib/restforce/concerns/base.rb +2 -2
- data/lib/restforce/concerns/caching.rb +7 -0
- data/lib/restforce/concerns/composite_api.rb +104 -0
- data/lib/restforce/concerns/picklists.rb +2 -2
- data/lib/restforce/concerns/streaming.rb +1 -3
- data/lib/restforce/config.rb +4 -1
- data/lib/restforce/error_code.rb +647 -0
- data/lib/restforce/file_part.rb +24 -0
- data/lib/restforce/mash.rb +8 -3
- data/lib/restforce/middleware/caching.rb +1 -1
- data/lib/restforce/middleware/logger.rb +8 -7
- data/lib/restforce/middleware/raise_error.rb +3 -4
- data/lib/restforce/middleware.rb +2 -0
- data/lib/restforce/version.rb +1 -1
- data/lib/restforce.rb +6 -7
- data/restforce.gemspec +11 -20
- data/spec/fixtures/sobject/list_view_results_success_response.json +151 -0
- data/spec/integration/abstract_client_spec.rb +41 -32
- data/spec/integration/data/client_spec.rb +6 -2
- data/spec/spec_helper.rb +24 -1
- data/spec/support/client_integration.rb +7 -7
- data/spec/support/concerns.rb +1 -1
- data/spec/support/fixture_helpers.rb +1 -3
- data/spec/support/middleware.rb +1 -2
- data/spec/unit/collection_spec.rb +38 -2
- data/spec/unit/concerns/api_spec.rb +11 -11
- data/spec/unit/concerns/authentication_spec.rb +6 -6
- data/spec/unit/concerns/caching_spec.rb +26 -0
- data/spec/unit/concerns/composite_api_spec.rb +143 -0
- data/spec/unit/concerns/connection_spec.rb +2 -2
- data/spec/unit/concerns/streaming_spec.rb +4 -4
- data/spec/unit/config_spec.rb +1 -1
- data/spec/unit/error_code_spec.rb +61 -0
- data/spec/unit/mash_spec.rb +5 -0
- 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/authentication_spec.rb +11 -5
- data/spec/unit/middleware/gzip_spec.rb +2 -2
- data/spec/unit/middleware/raise_error_spec.rb +29 -10
- data/spec/unit/signed_request_spec.rb +1 -1
- metadata +41 -125
- data/lib/restforce/upload_io.rb +0 -9
@@ -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
|
@@ -35,7 +35,7 @@ module Restforce
|
|
35
35
|
@valid_for = options.delete(:valid_for)
|
36
36
|
raise "#{field} is not a dependent picklist" if @valid_for && !dependent?
|
37
37
|
|
38
|
-
|
38
|
+
super(picklist_values)
|
39
39
|
end
|
40
40
|
|
41
41
|
private
|
@@ -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
|
@@ -95,9 +95,7 @@ module Restforce
|
|
95
95
|
|
96
96
|
def replay_id(channel)
|
97
97
|
handler = @replay_handlers[channel]
|
98
|
-
if handler.is_a?(Integer)
|
99
|
-
handler # treat it as a scalar
|
100
|
-
elsif handler.respond_to?(:[])
|
98
|
+
if handler.respond_to?(:[]) && !handler.is_a?(Integer)
|
101
99
|
# Ask for the latest replayId for this channel
|
102
100
|
handler[channel]
|
103
101
|
else
|
data/lib/restforce/config.rb
CHANGED
@@ -151,11 +151,14 @@ module Restforce
|
|
151
151
|
option :request_headers
|
152
152
|
|
153
153
|
# Set a logger for when Restforce.log is set to true, defaulting to STDOUT
|
154
|
-
option :logger, default: ::Logger.new(
|
154
|
+
option :logger, default: ::Logger.new($stdout)
|
155
155
|
|
156
156
|
# Set a log level for logging when Restforce.log is set to true, defaulting to :debug
|
157
157
|
option :log_level, default: :debug
|
158
158
|
|
159
|
+
# Set use_cache to false to opt in to caching with client.with_caching
|
160
|
+
option :use_cache, default: true
|
161
|
+
|
159
162
|
def options
|
160
163
|
self.class.options
|
161
164
|
end
|