restforce 4.2.2 → 5.0.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 +9 -9
- data/.github/ISSUE_TEMPLATE/unhandled-salesforce-error.md +17 -0
- data/.rubocop.yml +4 -3
- data/CHANGELOG.md +46 -0
- data/CONTRIBUTING.md +21 -1
- data/Dockerfile +31 -0
- data/Gemfile +0 -1
- data/README.md +43 -19
- data/UPGRADING.md +38 -0
- data/docker-compose.yml +7 -0
- data/lib/restforce.rb +4 -7
- data/lib/restforce/collection.rb +7 -2
- data/lib/restforce/concerns/api.rb +1 -1
- data/lib/restforce/concerns/caching.rb +7 -0
- data/lib/restforce/concerns/picklists.rb +1 -1
- data/lib/restforce/concerns/streaming.rb +1 -3
- data/lib/restforce/config.rb +4 -1
- data/lib/restforce/error_code.rb +617 -0
- data/lib/restforce/file_part.rb +24 -0
- data/lib/restforce/mash.rb +7 -2
- data/lib/restforce/middleware.rb +2 -0
- data/lib/restforce/middleware/caching.rb +1 -1
- data/lib/restforce/middleware/logger.rb +3 -2
- data/lib/restforce/middleware/raise_error.rb +3 -4
- data/lib/restforce/version.rb +1 -1
- data/restforce.gemspec +12 -12
- data/spec/integration/abstract_client_spec.rb +16 -4
- data/spec/spec_helper.rb +14 -1
- data/spec/support/fixture_helpers.rb +1 -3
- data/spec/unit/collection_spec.rb +18 -0
- data/spec/unit/concerns/caching_spec.rb +26 -0
- data/spec/unit/concerns/connection_spec.rb +2 -2
- data/spec/unit/error_code_spec.rb +61 -0
- data/spec/unit/middleware/authentication_spec.rb +11 -5
- data/spec/unit/middleware/raise_error_spec.rb +9 -0
- data/spec/unit/signed_request_spec.rb +1 -1
- metadata +34 -41
- data/lib/restforce/upload_io.rb +0 -9
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'faraday/file_part'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
require 'faraday/upload_io'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Restforce
|
|
10
|
+
if defined?(::Faraday::FilePart)
|
|
11
|
+
FilePart = Faraday::FilePart
|
|
12
|
+
|
|
13
|
+
# Deprecated
|
|
14
|
+
UploadIO = Faraday::FilePart
|
|
15
|
+
else
|
|
16
|
+
# Handle pre-1.0 versions of faraday
|
|
17
|
+
FilePart = Faraday::UploadIO
|
|
18
|
+
UploadIO = Faraday::UploadIO
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# This patch is only needed with multipart-post < 2.0.0
|
|
23
|
+
# 2.0.0 was released in 2013.
|
|
24
|
+
require 'restforce/patches/parts' unless Parts::Part.method(:new).arity.abs == 4
|
data/lib/restforce/mash.rb
CHANGED
|
@@ -11,9 +11,10 @@ module Restforce
|
|
|
11
11
|
# appropriate Restforce::Collection, Restforce::SObject and
|
|
12
12
|
# Restforce::Mash objects.
|
|
13
13
|
def build(val, client)
|
|
14
|
-
|
|
14
|
+
case val
|
|
15
|
+
when Array
|
|
15
16
|
val.collect { |a_val| self.build(a_val, client) }
|
|
16
|
-
|
|
17
|
+
when Hash
|
|
17
18
|
self.klass(val).new(val, client)
|
|
18
19
|
else
|
|
19
20
|
val
|
|
@@ -55,6 +56,9 @@ module Restforce
|
|
|
55
56
|
self.class.new(self, @client, self.default)
|
|
56
57
|
end
|
|
57
58
|
|
|
59
|
+
# The #convert_value method and its signature are part of Hashie::Mash's API, so we
|
|
60
|
+
# can't unilaterally decide to change `duping` to be a keyword argument
|
|
61
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
58
62
|
def convert_value(val, duping = false)
|
|
59
63
|
case val
|
|
60
64
|
when self.class
|
|
@@ -68,5 +72,6 @@ module Restforce
|
|
|
68
72
|
val
|
|
69
73
|
end
|
|
70
74
|
end
|
|
75
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
71
76
|
end
|
|
72
77
|
end
|
data/lib/restforce/middleware.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Restforce
|
|
|
11
11
|
@options = options
|
|
12
12
|
@logger = logger || begin
|
|
13
13
|
require 'logger'
|
|
14
|
-
::Logger.new(
|
|
14
|
+
::Logger.new($stdout)
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -36,7 +36,8 @@ module Restforce
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def dump(hash)
|
|
39
|
-
|
|
39
|
+
dumped_pairs = hash.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
|
|
40
|
+
"\n#{dumped_pairs}"
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
end
|
|
@@ -11,9 +11,9 @@ module Restforce
|
|
|
11
11
|
response_values
|
|
12
12
|
)
|
|
13
13
|
when 401
|
|
14
|
-
raise Restforce::UnauthorizedError,
|
|
14
|
+
raise Restforce::UnauthorizedError.new(message, response_values)
|
|
15
15
|
when 404
|
|
16
|
-
raise Restforce::NotFoundError,
|
|
16
|
+
raise Restforce::NotFoundError.new(message, response_values)
|
|
17
17
|
when 413
|
|
18
18
|
raise Restforce::EntityTooLargeError.new(
|
|
19
19
|
"413: Request Entity Too Large",
|
|
@@ -56,8 +56,7 @@ module Restforce
|
|
|
56
56
|
def exception_class_for_error_code(error_code)
|
|
57
57
|
return Restforce::ResponseError unless ERROR_CODE_MATCHER.match?(error_code)
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
Restforce::ErrorCode.const_get(constant_name)
|
|
59
|
+
Restforce::ErrorCode.get_exception_class(error_code)
|
|
61
60
|
end
|
|
62
61
|
end
|
|
63
62
|
end
|
data/lib/restforce/version.rb
CHANGED
data/restforce.gemspec
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
require File.expand_path('lib/restforce/version', __dir__)
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |gem|
|
|
6
|
-
gem.authors = ["Eric J. Holmes"
|
|
7
|
-
gem.email = ["
|
|
8
|
-
gem.description = 'A lightweight
|
|
9
|
-
gem.summary = 'A lightweight
|
|
10
|
-
gem.homepage = "
|
|
6
|
+
gem.authors = ["Tim Rogers", "Eric J. Holmes"]
|
|
7
|
+
gem.email = ["me@timrogers.co.uk", "eric@ejholmes.net"]
|
|
8
|
+
gem.description = 'A lightweight Ruby client for the Salesforce REST API'
|
|
9
|
+
gem.summary = 'A lightweight Ruby client for the Salesforce REST API'
|
|
10
|
+
gem.homepage = "https://restforce.github.io/"
|
|
11
11
|
gem.license = "MIT"
|
|
12
12
|
|
|
13
13
|
gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
|
@@ -22,12 +22,11 @@ Gem::Specification.new do |gem|
|
|
|
22
22
|
'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
gem.required_ruby_version = '>= 2.
|
|
25
|
+
gem.required_ruby_version = '>= 2.5'
|
|
26
26
|
|
|
27
|
-
gem.add_dependency 'faraday', '<=
|
|
28
|
-
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<=
|
|
27
|
+
gem.add_dependency 'faraday', '<= 2.0', '>= 0.9.0'
|
|
28
|
+
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 2.0']
|
|
29
29
|
|
|
30
|
-
gem.add_dependency 'json', '>= 1.7.5'
|
|
31
30
|
gem.add_dependency 'jwt', ['>= 1.5.6']
|
|
32
31
|
|
|
33
32
|
gem.add_dependency 'hashie', '>= 1.2.0', '< 5.0'
|
|
@@ -35,7 +34,8 @@ Gem::Specification.new do |gem|
|
|
|
35
34
|
gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
|
|
36
35
|
gem.add_development_dependency 'rspec', '~> 2.14.0'
|
|
37
36
|
gem.add_development_dependency 'rspec_junit_formatter', '~> 0.4.1'
|
|
38
|
-
|
|
39
|
-
gem.add_development_dependency '
|
|
40
|
-
gem.add_development_dependency '
|
|
37
|
+
|
|
38
|
+
gem.add_development_dependency 'rubocop', '~> 1.8.1'
|
|
39
|
+
gem.add_development_dependency 'simplecov', '~> 0.21.2'
|
|
40
|
+
gem.add_development_dependency 'webmock', '~> 3.11.1'
|
|
41
41
|
end
|
|
@@ -86,22 +86,34 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
context 'with multipart' do
|
|
89
|
-
# rubocop:disable
|
|
89
|
+
# rubocop:disable Layout/LineLength
|
|
90
90
|
requests 'sobjects/Account',
|
|
91
91
|
method: :post,
|
|
92
|
-
with_body: %r(----boundary_string\r\nContent-Disposition: form-data; name
|
|
92
|
+
with_body: %r(----boundary_string\r\nContent-Disposition: form-data; name="entity_content"\r\nContent-Type: application/json\r\n\r\n{"Name":"Foobar"}\r\n----boundary_string\r\nContent-Disposition: form-data; name="Blob"; filename="blob.jpg"\r\nContent-Length: 42171\r\nContent-Type: image/jpeg\r\nContent-Transfer-Encoding: binary),
|
|
93
93
|
fixture: 'sobject/create_success_response'
|
|
94
|
-
# rubocop:enable
|
|
94
|
+
# rubocop:enable Layout/LineLength
|
|
95
95
|
|
|
96
96
|
subject do
|
|
97
97
|
client.create('Account', Name: 'Foobar',
|
|
98
|
-
Blob: Restforce::
|
|
98
|
+
Blob: Restforce::FilePart.new(
|
|
99
99
|
File.expand_path('../fixtures/blob.jpg', __dir__),
|
|
100
100
|
'image/jpeg'
|
|
101
101
|
))
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
it { should eq 'some_id' }
|
|
105
|
+
|
|
106
|
+
context 'with deprecated UploadIO' do
|
|
107
|
+
subject do
|
|
108
|
+
client.create('Account', Name: 'Foobar',
|
|
109
|
+
Blob: Restforce::UploadIO.new(
|
|
110
|
+
File.expand_path('../fixtures/blob.jpg', __dir__),
|
|
111
|
+
'image/jpeg'
|
|
112
|
+
))
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it { should eq 'some_id' }
|
|
116
|
+
end
|
|
105
117
|
end
|
|
106
118
|
end
|
|
107
119
|
|
data/spec/spec_helper.rb
CHANGED
|
@@ -15,8 +15,21 @@ RSpec.configure do |config|
|
|
|
15
15
|
config.order = 'random'
|
|
16
16
|
config.filter_run focus: true
|
|
17
17
|
config.run_all_when_everything_filtered = true
|
|
18
|
+
|
|
19
|
+
original_stderr = $stderr
|
|
20
|
+
original_stdout = $stdout
|
|
21
|
+
config.before(:all) do
|
|
22
|
+
# Redirect stderr and stdout
|
|
23
|
+
$stderr = File.open(File::NULL, "w")
|
|
24
|
+
$stdout = File.open(File::NULL, "w")
|
|
25
|
+
end
|
|
26
|
+
config.after(:all) do
|
|
27
|
+
$stderr = original_stderr
|
|
28
|
+
$stdout = original_stdout
|
|
29
|
+
end
|
|
18
30
|
end
|
|
19
31
|
|
|
20
32
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
|
21
33
|
# in spec/support/ and its subdirectories.
|
|
22
|
-
Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")]
|
|
34
|
+
paths = Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")]
|
|
35
|
+
paths.sort.each { |f| require f }
|
|
@@ -22,9 +22,7 @@ module FixtureHelpers
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def stub_login_request(*)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
stub
|
|
25
|
+
stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
|
|
28
26
|
end
|
|
29
27
|
|
|
30
28
|
def fixture(filename)
|
|
@@ -62,4 +62,22 @@ describe Restforce::Collection do
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
describe '#empty?' do
|
|
67
|
+
subject(:empty?) do
|
|
68
|
+
described_class.new(JSON.parse(fixture(sobject_fixture)), client).empty?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context 'with size 1' do
|
|
72
|
+
let(:sobject_fixture) { 'sobject/query_success_response' }
|
|
73
|
+
|
|
74
|
+
it { should be_false }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context 'with size 0' do
|
|
78
|
+
let(:sobject_fixture) { 'sobject/query_empty_response' }
|
|
79
|
+
|
|
80
|
+
it { should be_true }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
65
83
|
end
|
|
@@ -28,4 +28,30 @@ describe Restforce::Concerns::Caching do
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
describe '.with_caching' do
|
|
33
|
+
let(:options) { double('Options') }
|
|
34
|
+
|
|
35
|
+
before do
|
|
36
|
+
client.stub options: options
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'runs the block with caching enabled' do
|
|
40
|
+
options.should_receive(:[]=).with(:use_cache, true)
|
|
41
|
+
options.should_receive(:[]=).with(:use_cache, false)
|
|
42
|
+
expect { |b| client.with_caching(&b) }.to yield_control
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'when an exception is raised' do
|
|
46
|
+
it 'ensures the :use_cache is set to false' do
|
|
47
|
+
options.should_receive(:[]=).with(:use_cache, true)
|
|
48
|
+
options.should_receive(:[]=).with(:use_cache, false)
|
|
49
|
+
expect {
|
|
50
|
+
client.with_caching do
|
|
51
|
+
raise 'Foo'
|
|
52
|
+
end
|
|
53
|
+
}.to raise_error 'Foo'
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
31
57
|
end
|
|
@@ -73,9 +73,9 @@ describe Restforce::Concerns::Connection do
|
|
|
73
73
|
Restforce.stub(log?: true)
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
it "must always be used
|
|
76
|
+
it "must always be used as the last handler" do
|
|
77
77
|
client.middleware.handlers.reverse.index(Restforce::Middleware::Logger).
|
|
78
|
-
should eq
|
|
78
|
+
should eq 0
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Restforce::ErrorCode do
|
|
6
|
+
describe "mapping of error codes to classes" do
|
|
7
|
+
subject(:error_exception_classes) { described_class::ERROR_EXCEPTION_CLASSES }
|
|
8
|
+
|
|
9
|
+
let(:exception_classes) do
|
|
10
|
+
described_class.constants.
|
|
11
|
+
map { |constant_name| described_class.const_get(constant_name) }.
|
|
12
|
+
select { |constant| constant.is_a?(Class) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "maps all defined exception classes to an error code" do
|
|
16
|
+
exception_classes.each do |exception_class|
|
|
17
|
+
expect(error_exception_classes.values).to include(exception_class)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "maps all error codes to a defined exception class" do
|
|
22
|
+
error_exception_classes.each_value do |mapped_exception_class|
|
|
23
|
+
expect(exception_classes).to include(mapped_exception_class)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '.get_exception_class' do
|
|
29
|
+
context 'when a non-existent error code is looked up' do
|
|
30
|
+
let(:new_error_code) { 'ANOTHER_NEW_ERROR_CODE' }
|
|
31
|
+
subject { described_class.get_exception_class(new_error_code) }
|
|
32
|
+
|
|
33
|
+
it { should be Restforce::ResponseError }
|
|
34
|
+
|
|
35
|
+
it 'outputs a warning' do
|
|
36
|
+
expect(Warning).to receive(:warn)
|
|
37
|
+
subject
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'when a known error code is looked up' do
|
|
42
|
+
let(:existing_error_code) { "ALL_OR_NONE_OPERATION_ROLLED_BACK" }
|
|
43
|
+
let(:existing_error) { described_class::AllOrNoneOperationRolledBack }
|
|
44
|
+
|
|
45
|
+
subject do
|
|
46
|
+
described_class.get_exception_class(existing_error_code)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it { should < Restforce::ResponseError }
|
|
50
|
+
|
|
51
|
+
it 'returns existing error' do
|
|
52
|
+
should be(existing_error)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'does not output a warning' do
|
|
56
|
+
expect(Warning).to_not receive(:warn)
|
|
57
|
+
subject
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -8,7 +8,9 @@ describe Restforce::Middleware::Authentication do
|
|
|
8
8
|
proxy_uri: 'https://not-a-real-site.com',
|
|
9
9
|
authentication_retries: retries,
|
|
10
10
|
adapter: :net_http,
|
|
11
|
+
# rubocop:disable Naming/VariableNumber
|
|
11
12
|
ssl: { version: :TLSv1_2 } }
|
|
13
|
+
# rubocop:enable Naming/VariableNumber
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
describe '.authenticate!' do
|
|
@@ -57,10 +59,10 @@ describe Restforce::Middleware::Authentication do
|
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
its(:handlers) {
|
|
60
|
-
should include FaradayMiddleware::ParseJson
|
|
61
|
-
Faraday::Adapter::NetHttp
|
|
62
|
+
should include FaradayMiddleware::ParseJson
|
|
62
63
|
}
|
|
63
64
|
its(:handlers) { should_not include Restforce::Middleware::Logger }
|
|
65
|
+
its(:adapter) { should eq Faraday::Adapter::NetHttp }
|
|
64
66
|
end
|
|
65
67
|
|
|
66
68
|
context 'with logging enabled' do
|
|
@@ -70,8 +72,9 @@ describe Restforce::Middleware::Authentication do
|
|
|
70
72
|
|
|
71
73
|
its(:handlers) {
|
|
72
74
|
should include FaradayMiddleware::ParseJson,
|
|
73
|
-
Restforce::Middleware::Logger
|
|
75
|
+
Restforce::Middleware::Logger
|
|
74
76
|
}
|
|
77
|
+
its(:adapter) { should eq Faraday::Adapter::NetHttp }
|
|
75
78
|
end
|
|
76
79
|
|
|
77
80
|
context 'with specified adapter' do
|
|
@@ -80,13 +83,16 @@ describe Restforce::Middleware::Authentication do
|
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
its(:handlers) {
|
|
83
|
-
should include FaradayMiddleware::ParseJson
|
|
86
|
+
should include FaradayMiddleware::ParseJson
|
|
84
87
|
}
|
|
88
|
+
its(:adapter) { should eq Faraday::Adapter::Typhoeus }
|
|
85
89
|
end
|
|
86
90
|
end
|
|
87
91
|
|
|
88
92
|
it "should have SSL config set" do
|
|
93
|
+
# rubocop:disable Naming/VariableNumber
|
|
89
94
|
connection.ssl[:version].should eq(:TLSv1_2)
|
|
95
|
+
# rubocop:enable Naming/VariableNumber
|
|
90
96
|
end
|
|
91
97
|
end
|
|
92
98
|
|
|
@@ -94,7 +100,7 @@ describe Restforce::Middleware::Authentication do
|
|
|
94
100
|
context 'when response.body is present' do
|
|
95
101
|
let(:response) {
|
|
96
102
|
Faraday::Response.new(
|
|
97
|
-
|
|
103
|
+
response_body: { 'error' => 'error', 'error_description' => 'description' },
|
|
98
104
|
status: 401
|
|
99
105
|
)
|
|
100
106
|
}
|
|
@@ -85,5 +85,14 @@ describe Restforce::Middleware::RaiseError do
|
|
|
85
85
|
"(error code missing): #{body}"
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
|
+
|
|
89
|
+
context 'when error code is not already defined' do
|
|
90
|
+
let(:body) { { 'errorCode' => 'SOMETHING_UNDEFINED' } }
|
|
91
|
+
let(:status) { 400 }
|
|
92
|
+
|
|
93
|
+
it 'raises a generic Restforce::ResponseError' do
|
|
94
|
+
expect { on_complete }.to raise_error Restforce::ResponseError
|
|
95
|
+
end
|
|
96
|
+
end
|
|
88
97
|
end
|
|
89
98
|
end
|