restforce 5.0.6 → 6.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +4 -13
- data/.github/funding.yml +1 -0
- data/.github/workflows/build.yml +23 -0
- data/.github/workflows/faraday.yml +27 -0
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +68 -0
- data/Gemfile +15 -6
- data/README.md +61 -7
- data/UPGRADING.md +29 -0
- data/lib/restforce/abstract_client.rb +1 -0
- data/lib/restforce/collection.rb +20 -2
- data/lib/restforce/concerns/api.rb +2 -1
- data/lib/restforce/concerns/base.rb +2 -2
- data/lib/restforce/concerns/composite_api.rb +104 -0
- data/lib/restforce/concerns/connection.rb +1 -1
- data/lib/restforce/concerns/picklists.rb +1 -1
- data/lib/restforce/config.rb +12 -10
- data/lib/restforce/error_code.rb +30 -9
- data/lib/restforce/file_part.rb +12 -4
- data/lib/restforce/middleware/authentication.rb +1 -0
- data/lib/restforce/middleware/caching.rb +140 -15
- data/lib/restforce/middleware/gzip.rb +4 -0
- data/lib/restforce/middleware/json_request.rb +90 -0
- data/lib/restforce/middleware/json_response.rb +85 -0
- data/lib/restforce/middleware/logger.rb +6 -2
- data/lib/restforce/middleware/raise_error.rb +10 -1
- data/lib/restforce/version.rb +1 -1
- data/lib/restforce.rb +11 -7
- data/restforce.gemspec +8 -16
- data/spec/fixtures/sobject/list_view_results_success_response.json +151 -0
- data/spec/integration/abstract_client_spec.rb +42 -30
- 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 +22 -4
- data/spec/unit/concerns/api_spec.rb +22 -15
- data/spec/unit/concerns/authentication_spec.rb +6 -6
- data/spec/unit/concerns/base_spec.rb +1 -1
- data/spec/unit/concerns/composite_api_spec.rb +169 -0
- data/spec/unit/concerns/connection_spec.rb +1 -1
- data/spec/unit/concerns/streaming_spec.rb +4 -4
- data/spec/unit/config_spec.rb +2 -2
- data/spec/unit/middleware/authentication/jwt_bearer_spec.rb +24 -8
- data/spec/unit/middleware/authentication/password_spec.rb +12 -4
- data/spec/unit/middleware/authentication/token_spec.rb +12 -4
- data/spec/unit/middleware/authentication_spec.rb +8 -8
- data/spec/unit/middleware/authorization_spec.rb +5 -1
- data/spec/unit/middleware/custom_headers_spec.rb +6 -2
- data/spec/unit/middleware/gzip_spec.rb +60 -16
- data/spec/unit/middleware/instance_url_spec.rb +2 -2
- data/spec/unit/middleware/logger_spec.rb +1 -1
- data/spec/unit/middleware/raise_error_spec.rb +20 -10
- data/spec/unit/sobject_spec.rb +9 -5
- metadata +55 -172
- data/.circleci/config.yml +0 -56
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
|
@@ -143,6 +145,8 @@ module Restforce
|
|
143
145
|
|
144
146
|
class ErrorInMailer < ResponseError; end
|
145
147
|
|
148
|
+
class ExceededIdLimit < ResponseError; end
|
149
|
+
|
146
150
|
class ExceededMaxSemijoinSubselects < ResponseError; end
|
147
151
|
|
148
152
|
class FailedActivation < ResponseError; end
|
@@ -237,10 +241,14 @@ module Restforce
|
|
237
241
|
|
238
242
|
class InvalidReadOnlyUserDml < ResponseError; end
|
239
243
|
|
244
|
+
class InvalidReplicationDate < ResponseError; end
|
245
|
+
|
240
246
|
class InvalidSaveAsActivityFlag < ResponseError; end
|
241
247
|
|
242
248
|
class InvalidSessionId < ResponseError; end
|
243
249
|
|
250
|
+
class InvalidSignupCountry < ResponseError; end
|
251
|
+
|
244
252
|
class InvalidStatus < ResponseError; end
|
245
253
|
|
246
254
|
class InvalidType < ResponseError; end
|
@@ -271,6 +279,8 @@ module Restforce
|
|
271
279
|
|
272
280
|
class MalformedQuery < ResponseError; end
|
273
281
|
|
282
|
+
class MalformedSearch < ResponseError; end
|
283
|
+
|
274
284
|
class ManagerNotDefined < ResponseError; end
|
275
285
|
|
276
286
|
class MassmailRetryLimitExceeded < ResponseError; end
|
@@ -327,6 +337,8 @@ module Restforce
|
|
327
337
|
|
328
338
|
class OpWithInvalidUserTypeException < ResponseError; end
|
329
339
|
|
340
|
+
class OperationTooLarge < ResponseError; end
|
341
|
+
|
330
342
|
class OptedOutOfMassMail < ResponseError; end
|
331
343
|
|
332
344
|
class PackageLicenseRequired < ResponseError; end
|
@@ -341,6 +353,8 @@ module Restforce
|
|
341
353
|
|
342
354
|
class PrivateContactOnAsset < ResponseError; end
|
343
355
|
|
356
|
+
class QueryTimeout < ResponseError; end
|
357
|
+
|
344
358
|
class RecordInUseByWorkflow < ResponseError; end
|
345
359
|
|
346
360
|
class RequestLimitExceeded < ResponseError; end
|
@@ -411,6 +425,7 @@ module Restforce
|
|
411
425
|
BccNotAllowedIfBccComplianceEnabled,
|
412
426
|
"BCC_SELF_NOT_ALLOWED_IF_BCC_COMPLIANCE_ENABLED" =>
|
413
427
|
BccSelfNotAllowedIfBccComplianceEnabled,
|
428
|
+
"BIG_OBJECT_UNSUPPORTED_OPERATION" => BigObjectUnsupportedOperation,
|
414
429
|
"CANNOT_CASCADE_PRODUCT_ACTIVE" => CannotCascadeProductActive,
|
415
430
|
"CANNOT_CHANGE_FIELD_TYPE_OF_APEX_REFERENCED_FIELD" =>
|
416
431
|
CannotChangeFieldTypeOfApexReferencedField,
|
@@ -471,6 +486,7 @@ module Restforce
|
|
471
486
|
"ENTITY_IS_LOCKED" => EntityIsLocked,
|
472
487
|
"ENVIRONMENT_HUB_MEMBERSHIP_CONFLICT" => EnvironmentHubMembershipConflict,
|
473
488
|
"ERROR_IN_MAILER" => ErrorInMailer,
|
489
|
+
"EXCEEDED_ID_LIMIT" => ExceededIdLimit,
|
474
490
|
"EXCEEDED_MAX_SEMIJOIN_SUBSELECTS" => ExceededMaxSemijoinSubselects,
|
475
491
|
"FAILED_ACTIVATION" => FailedActivation,
|
476
492
|
"FIELD_CUSTOM_VALIDATION_EXCEPTION" => FieldCustomValidationException,
|
@@ -521,8 +537,10 @@ module Restforce
|
|
521
537
|
"INVALID_PARTNER_NETWORK_STATUS" => InvalidPartnerNetworkStatus,
|
522
538
|
"INVALID_PERSON_ACCOUNT_OPERATION" => InvalidPersonAccountOperation,
|
523
539
|
"INVALID_READ_ONLY_USER_DML" => InvalidReadOnlyUserDml,
|
540
|
+
"INVALID_REPLICATION_DATE" => InvalidReplicationDate,
|
524
541
|
"INVALID_SAVE_AS_ACTIVITY_FLAG" => InvalidSaveAsActivityFlag,
|
525
542
|
"INVALID_SESSION_ID" => InvalidSessionId,
|
543
|
+
"INVALID_SIGNUP_COUNTRY" => InvalidSignupCountry,
|
526
544
|
"INVALID_STATUS" => InvalidStatus,
|
527
545
|
"INVALID_TYPE" => InvalidType,
|
528
546
|
"INVALID_TYPE_FOR_OPERATION" => InvalidTypeForOperation,
|
@@ -538,6 +556,7 @@ module Restforce
|
|
538
556
|
"LOGIN_MUST_USE_SECURITY_TOKEN" => LoginMustUseSecurityToken,
|
539
557
|
"MALFORMED_ID" => MalformedId,
|
540
558
|
"MALFORMED_QUERY" => MalformedQuery,
|
559
|
+
"MALFORMED_SEARCH" => MalformedSearch,
|
541
560
|
"MANAGER_NOT_DEFINED" => ManagerNotDefined,
|
542
561
|
"MASSMAIL_RETRY_LIMIT_EXCEEDED" => MassmailRetryLimitExceeded,
|
543
562
|
"MASS_MAIL_LIMIT_EXCEEDED" => MassMailLimitExceeded,
|
@@ -566,6 +585,7 @@ module Restforce
|
|
566
585
|
"NUMBER_OUTSIDE_VALID_RANGE" => NumberOutsideValidRange,
|
567
586
|
"NUM_HISTORY_FIELDS_BY_SOBJECT_EXCEEDED" => NumHistoryFieldsBySobjectExceeded,
|
568
587
|
"OP_WITH_INVALID_USER_TYPE_EXCEPTION" => OpWithInvalidUserTypeException,
|
588
|
+
"OPERATION_TOO_LARGE" => OperationTooLarge,
|
569
589
|
"OPTED_OUT_OF_MASS_MAIL" => OptedOutOfMassMail,
|
570
590
|
"PACKAGE_LICENSE_REQUIRED" => PackageLicenseRequired,
|
571
591
|
"PLATFORM_EVENT_ENCRYPTION_ERROR" => PlatformEventEncryptionError,
|
@@ -573,6 +593,7 @@ module Restforce
|
|
573
593
|
"PLATFORM_EVENT_PUBLISH_FAILED" => PlatformEventPublishFailed,
|
574
594
|
"PORTAL_USER_ALREADY_EXISTS_FOR_CONTACT" => PortalUserAlreadyExistsForContact,
|
575
595
|
"PRIVATE_CONTACT_ON_ASSET" => PrivateContactOnAsset,
|
596
|
+
"QUERY_TIMEOUT" => QueryTimeout,
|
576
597
|
"RECORD_IN_USE_BY_WORKFLOW" => RecordInUseByWorkflow,
|
577
598
|
"REQUEST_LIMIT_EXCEEDED" => RequestLimitExceeded,
|
578
599
|
"REQUEST_RUNNING_TOO_LONG" => RequestRunningTooLong,
|
@@ -606,9 +627,9 @@ module Restforce
|
|
606
627
|
def self.get_exception_class(error_code)
|
607
628
|
ERROR_EXCEPTION_CLASSES.fetch(error_code) do |_|
|
608
629
|
warn "[restforce] An unrecognised error code, `#{error_code}` has been " \
|
609
|
-
|
610
|
-
|
611
|
-
|
630
|
+
"received from Salesforce. Instead of raising an error-specific exception" \
|
631
|
+
", we'll raise a generic `ResponseError`. Please report this missing " \
|
632
|
+
"error code on GitHub at <#{GITHUB_ISSUE_URL}>."
|
612
633
|
|
613
634
|
# If we've received an unexpected error where we don't have a specific
|
614
635
|
# class defined, we can return a generic ResponseError instead
|
@@ -618,10 +639,10 @@ module Restforce
|
|
618
639
|
|
619
640
|
def self.const_missing(constant_name)
|
620
641
|
warn "[restforce] You're referring to a Restforce error that isn't defined, " \
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
642
|
+
"`#{name}::#{constant_name}` (for example by trying to `rescue` it). This " \
|
643
|
+
"might be our fault - we've recently made some changes to how errors are " \
|
644
|
+
"defined. If you're sure that this is a valid Salesforce error, then " \
|
645
|
+
"please create an issue on GitHub at <#{GITHUB_ISSUE_URL}>."
|
625
646
|
|
626
647
|
super(constant_name)
|
627
648
|
end
|
data/lib/restforce/file_part.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
case Faraday::VERSION
|
4
|
+
when /\A1\.[0-8]\./
|
5
|
+
# Faraday 1.x versions before 1.9 - not matched by
|
6
|
+
# the previous clause - use `FilePart` (which must be explicitly
|
7
|
+
# required)
|
4
8
|
require 'faraday/file_part'
|
5
|
-
|
6
|
-
|
9
|
+
when /\A[12]\./
|
10
|
+
# Later 1.x versions from 1.9 onwards automatically include the
|
11
|
+
# `faraday-multipart` gem, which includes `Faraday::FilePart`
|
12
|
+
require 'faraday/multipart'
|
13
|
+
else
|
14
|
+
raise "Unexpected Faraday version #{Faraday::VERSION} - not sure how to set up " \
|
15
|
+
"multipart support"
|
7
16
|
end
|
8
|
-
|
9
17
|
module Restforce
|
10
18
|
if defined?(::Faraday::FilePart)
|
11
19
|
FilePart = Faraday::FilePart
|
@@ -49,6 +49,7 @@ module Restforce
|
|
49
49
|
@connection ||= Faraday.new(faraday_options) do |builder|
|
50
50
|
builder.use Faraday::Request::UrlEncoded
|
51
51
|
builder.use Restforce::Middleware::Mashify, nil, @options
|
52
|
+
builder.use Faraday::FollowRedirects::Middleware
|
52
53
|
builder.response :json
|
53
54
|
|
54
55
|
if Restforce.log?
|
@@ -1,30 +1,155 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
#
|
4
|
+
# Taken from `lib/faraday_middleware/response/caching.rb` in the `faraday_middleware`
|
5
|
+
# gem (<https://github.com/lostisland/faraday_middleware/blob/784b4f8/lib/faraday_middleware/response/caching.rb>).
|
6
|
+
#
|
7
|
+
# Includes some small modifications to allow the cache to be cleared in
|
8
|
+
# `#without_caching` mode, replicating traditional Restforce behaviour.
|
9
|
+
#
|
10
|
+
# Copyright (c) 2011 Erik Michaels-Ober, Wynn Netherland, et al.
|
11
|
+
# Licensed under the MIT License.
|
12
|
+
#
|
13
|
+
require 'faraday'
|
14
|
+
require 'forwardable'
|
15
|
+
require 'digest/sha1'
|
16
|
+
|
3
17
|
module Restforce
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
18
|
+
# Public: Caches GET responses and pulls subsequent ones from the cache.
|
19
|
+
class Middleware::Caching < Faraday::Middleware
|
20
|
+
attr_reader :cache
|
21
|
+
|
22
|
+
# Internal: List of status codes that can be cached:
|
23
|
+
# * 200 - 'OK'
|
24
|
+
# * 203 - 'Non-Authoritative Information'
|
25
|
+
# * 300 - 'Multiple Choices'
|
26
|
+
# * 301 - 'Moved Permanently'
|
27
|
+
# * 302 - 'Found'
|
28
|
+
# * 404 - 'Not Found'
|
29
|
+
# * 410 - 'Gone'
|
30
|
+
CACHEABLE_STATUS_CODES = [200, 203, 300, 301, 302, 404, 410].freeze
|
31
|
+
|
32
|
+
extend Forwardable
|
33
|
+
def_delegators :'Faraday::Utils', :parse_query, :build_query
|
34
|
+
|
35
|
+
# Public: initialize the middleware.
|
36
|
+
#
|
37
|
+
# cache - An object that responds to read and write (default: nil).
|
38
|
+
# options - An options Hash (default: {}):
|
39
|
+
# :ignore_params - String name or Array names of query
|
40
|
+
# params that should be ignored when forming
|
41
|
+
# the cache key (default: []).
|
42
|
+
# :write_options - Hash of settings that should be passed as the
|
43
|
+
# third options parameter to the cache's #write
|
44
|
+
# method. If not specified, no options parameter
|
45
|
+
# will be passed.
|
46
|
+
# :full_key - Boolean - use full URL as cache key:
|
47
|
+
# (url.host + url.request_uri)
|
48
|
+
# :status_codes - Array of http status code to be cache
|
49
|
+
# (default: CACHEABLE_STATUS_CODE)
|
50
|
+
#
|
51
|
+
# Yields if no cache is given. The block should return a cache object.
|
52
|
+
def initialize(app, cache = nil, options = {})
|
53
|
+
super(app)
|
54
|
+
if cache.is_a?(Hash) && block_given?
|
55
|
+
options = cache
|
56
|
+
cache = nil
|
57
|
+
end
|
58
|
+
@cache = cache || yield
|
59
|
+
@options = options
|
8
60
|
end
|
9
61
|
|
10
|
-
def
|
11
|
-
|
62
|
+
def call(env)
|
63
|
+
# Taken from `Restforce::Middleware::Caching` implementation
|
64
|
+
# before we switched away from the `faraday_middleware` gem.
|
65
|
+
# See https://github.com/restforce/restforce/blob/a08b9d6c5e277bd7111ffa7ed50465dd49c05fab/lib/restforce/middleware/caching.rb.
|
66
|
+
cache&.delete(cache_key(env)) unless use_cache?
|
67
|
+
|
68
|
+
if env[:method] == :get
|
69
|
+
if env[:parallel_manager]
|
70
|
+
# callback mode
|
71
|
+
cache_on_complete(env)
|
72
|
+
else
|
73
|
+
# synchronous mode
|
74
|
+
key = cache_key(env)
|
75
|
+
unless (response = cache.read(key)) && response
|
76
|
+
response = @app.call(env)
|
77
|
+
store_response_in_cache(key, response)
|
78
|
+
end
|
79
|
+
finalize_response(response, env)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
@app.call(env)
|
83
|
+
end
|
12
84
|
end
|
13
85
|
|
14
|
-
# We don't want to cache requests for different clients, so append the
|
15
|
-
# oauth token to the cache key.
|
16
86
|
def cache_key(env)
|
17
|
-
|
87
|
+
url = env[:url].dup
|
88
|
+
if url.query && params_to_ignore.any?
|
89
|
+
params = parse_query url.query
|
90
|
+
params.reject! { |k,| params_to_ignore.include? k }
|
91
|
+
url.query = params.any? ? build_query(params) : nil
|
92
|
+
end
|
93
|
+
url.normalize!
|
94
|
+
digest = full_key? ? url.host + url.request_uri : url.request_uri
|
95
|
+
Digest::SHA1.hexdigest(digest)
|
18
96
|
end
|
19
97
|
|
20
|
-
def
|
21
|
-
@options[:
|
98
|
+
def params_to_ignore
|
99
|
+
@params_to_ignore ||= Array(@options[:ignore_params]).map(&:to_s)
|
100
|
+
end
|
101
|
+
|
102
|
+
def full_key?
|
103
|
+
@full_key ||= @options[:full_key]
|
22
104
|
end
|
23
105
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
106
|
+
def custom_status_codes
|
107
|
+
@custom_status_codes ||= begin
|
108
|
+
codes = CACHEABLE_STATUS_CODES & Array(@options[:status_codes]).map(&:to_i)
|
109
|
+
codes.any? ? codes : CACHEABLE_STATUS_CODES
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def cache_on_complete(env)
|
114
|
+
key = cache_key(env)
|
115
|
+
if (cached_response = cache.read(key))
|
116
|
+
finalize_response(cached_response, env)
|
117
|
+
else
|
118
|
+
# response.status is nil at this point
|
119
|
+
# any checks need to be done inside on_complete block
|
120
|
+
@app.call(env).on_complete do |response_env|
|
121
|
+
store_response_in_cache(key, response_env.response)
|
122
|
+
response_env
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def store_response_in_cache(key, response)
|
128
|
+
return unless custom_status_codes.include?(response.status)
|
129
|
+
|
130
|
+
if @options[:write_options]
|
131
|
+
cache.write(key, response, @options[:write_options])
|
132
|
+
else
|
133
|
+
cache.write(key, response)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def finalize_response(response, env)
|
138
|
+
response = response.dup if response.frozen?
|
139
|
+
env[:response] = response
|
140
|
+
unless env[:response_headers]
|
141
|
+
env.update response.env
|
142
|
+
# FIXME: omg hax
|
143
|
+
response.instance_variable_set('@env', env)
|
144
|
+
end
|
145
|
+
response
|
146
|
+
end
|
147
|
+
|
148
|
+
# Taken from `Restforce::Middleware::Caching` implementation
|
149
|
+
# before we switched away from the `faraday_middleware` gem.
|
150
|
+
# See https://github.com/restforce/restforce/blob/a08b9d6c5e277bd7111ffa7ed50465dd49c05fab/lib/restforce/middleware/caching.rb.
|
151
|
+
def use_cache?
|
152
|
+
@options[:use_cache]
|
28
153
|
end
|
29
154
|
end
|
30
155
|
end
|
@@ -28,6 +28,10 @@ module Restforce
|
|
28
28
|
# Internal: Decompresses a gzipped string.
|
29
29
|
def decompress(body)
|
30
30
|
Zlib::GzipReader.new(StringIO.new(body)).read
|
31
|
+
rescue Zlib::GzipFile::Error
|
32
|
+
# We thought the response was gzipped, but it wasn't. Return the original
|
33
|
+
# body back to the caller. See https://github.com/restforce/restforce/issues/761.
|
34
|
+
body
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Adapted from `lib/faraday/request/json.rb` in the `faraday`
|
5
|
+
# gem (<https://github.com/lostisland/faraday/blob/5366029282968d59980a182258f8c2b0212721c8/lib/faraday/request/json.rb>).
|
6
|
+
#
|
7
|
+
# We use this because we want to support Faraday 1.x and Faraday 2.x.
|
8
|
+
# Faraday 2.x has the JSON middlewares included, but Faraday 1.x doesn't,
|
9
|
+
# forcing you to use the `faraday_middleware` gem. This approach allows us
|
10
|
+
# to support both.
|
11
|
+
#
|
12
|
+
# Copyright (c) 2009-2022 Rick Olson, Zack Hobson
|
13
|
+
# Licensed under the MIT License.
|
14
|
+
#
|
15
|
+
require 'json'
|
16
|
+
|
17
|
+
module Restforce
|
18
|
+
# Request middleware that encodes the body as JSON.
|
19
|
+
#
|
20
|
+
# Processes only requests with matching Content-type or those without a type.
|
21
|
+
# If a request doesn't have a type but has a body, it sets the Content-type
|
22
|
+
# to JSON MIME-type.
|
23
|
+
#
|
24
|
+
# Doesn't try to encode bodies that already are in string form.
|
25
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
26
|
+
class Middleware::JsonRequest < Faraday::Middleware
|
27
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
28
|
+
|
29
|
+
# This is the only line that differs substantively from the version in
|
30
|
+
# Faraday. In Faraday, this refers to a Faraday constant.
|
31
|
+
CONTENT_TYPE = 'Content-Type'
|
32
|
+
|
33
|
+
MIME_TYPE = 'application/json'
|
34
|
+
MIME_TYPE_REGEX = %r{^application/(vnd\..+\+)?json$}.freeze
|
35
|
+
|
36
|
+
#
|
37
|
+
# Taken from `lib/faraday/middleware.rb` in the `faraday`
|
38
|
+
# gem (<https://github.com/lostisland/faraday/blob/08b7d4d/lib/faraday/middleware.rb>),
|
39
|
+
# with a tiny adaptation to refer to the `@app` instance
|
40
|
+
# variable rather than expecting an `attr_reader` to exist.
|
41
|
+
#
|
42
|
+
# In Faraday versions before v1.2.0, `#call` is missing.
|
43
|
+
#
|
44
|
+
# Copyright (c) 2009-2022 Rick Olson, Zack Hobson
|
45
|
+
# Licensed under the MIT License.
|
46
|
+
#
|
47
|
+
def call(env)
|
48
|
+
on_request(env) if respond_to?(:on_request)
|
49
|
+
@app.call(env).on_complete do |environment|
|
50
|
+
on_complete(environment) if respond_to?(:on_complete)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def on_request(env)
|
55
|
+
match_content_type(env) do |data|
|
56
|
+
env[:body] = encode(data)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def encode(data)
|
63
|
+
::JSON.generate(data)
|
64
|
+
end
|
65
|
+
|
66
|
+
def match_content_type(env)
|
67
|
+
return unless process_request?(env)
|
68
|
+
|
69
|
+
env[:request_headers][CONTENT_TYPE] ||= MIME_TYPE
|
70
|
+
yield env[:body] unless env[:body].respond_to?(:to_str)
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_request?(env)
|
74
|
+
type = request_type(env)
|
75
|
+
body?(env) && (type.empty? || type.match?(MIME_TYPE_REGEX))
|
76
|
+
end
|
77
|
+
|
78
|
+
def body?(env)
|
79
|
+
(body = env[:body]) && !(body.respond_to?(:to_str) && body.empty?)
|
80
|
+
end
|
81
|
+
|
82
|
+
def request_type(env)
|
83
|
+
type = env[:request_headers][CONTENT_TYPE].to_s
|
84
|
+
type = type.split(';', 2).first if type.index(';')
|
85
|
+
type
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
Faraday::Request.register_middleware(json: Restforce::Middleware::JsonRequest)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Adapted from `lib/faraday/response/json.rb` in the `faraday`
|
5
|
+
# gem (<https://github.com/lostisland/faraday/blob/5366029/lib/faraday/response/json.rb>).
|
6
|
+
#
|
7
|
+
# Copyright (c) 2009-2022 Rick Olson, Zack Hobson
|
8
|
+
# Licensed under the MIT License.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'json'
|
12
|
+
|
13
|
+
module Restforce
|
14
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
15
|
+
class Middleware::JsonResponse < Faraday::Middleware
|
16
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
17
|
+
|
18
|
+
# This is the only line that differs substantively from the version in
|
19
|
+
# Faraday. In Faraday, this refers to a Faraday constant.
|
20
|
+
CONTENT_TYPE = 'Content-Type'
|
21
|
+
|
22
|
+
def initialize(app = nil, parser_options: nil, content_type: /\bjson$/,
|
23
|
+
preserve_raw: false)
|
24
|
+
super(app)
|
25
|
+
@parser_options = parser_options
|
26
|
+
@content_types = Array(content_type)
|
27
|
+
@preserve_raw = preserve_raw
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Taken from `lib/faraday/middleware.rb` in the `faraday`
|
32
|
+
# gem (<https://github.com/lostisland/faraday/blob/08b7d4d/lib/faraday/middleware.rb>),
|
33
|
+
# with a tiny adaptation to refer to the `@app` instance
|
34
|
+
# variable rather than expecting an `attr_reader` to exist.
|
35
|
+
#
|
36
|
+
# In Faraday versions before v1.2.0, `#call` is missing.
|
37
|
+
#
|
38
|
+
# Copyright (c) 2009-2022 Rick Olson, Zack Hobson
|
39
|
+
# Licensed under the MIT License.
|
40
|
+
#
|
41
|
+
def call(env)
|
42
|
+
on_request(env) if respond_to?(:on_request)
|
43
|
+
@app.call(env).on_complete do |environment|
|
44
|
+
on_complete(environment) if respond_to?(:on_complete)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_complete(env)
|
49
|
+
process_response(env) if parse_response?(env)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def process_response(env)
|
55
|
+
env[:raw_body] = env[:body] if @preserve_raw
|
56
|
+
env[:body] = parse(env[:body])
|
57
|
+
rescue StandardError, SyntaxError => e
|
58
|
+
raise Faraday::ParsingError.new(e, env[:response])
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse(body)
|
62
|
+
::JSON.parse(body, @parser_options || {}) unless body.strip.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_response?(env)
|
66
|
+
process_response_type?(env) &&
|
67
|
+
env[:body].respond_to?(:to_str)
|
68
|
+
end
|
69
|
+
|
70
|
+
def process_response_type?(env)
|
71
|
+
type = response_type(env)
|
72
|
+
@content_types.empty? || @content_types.any? do |pattern|
|
73
|
+
pattern.is_a?(Regexp) ? type.match?(pattern) : type == pattern
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def response_type(env)
|
78
|
+
type = env[:response_headers][CONTENT_TYPE].to_s
|
79
|
+
type = type.split(';', 2).first if type.index(';')
|
80
|
+
type
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Faraday::Response.register_middleware(json: Restforce::Middleware::JsonResponse)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
5
|
module Restforce
|
6
|
-
class Middleware::Logger < Faraday::
|
6
|
+
class Middleware::Logger < Faraday::Middleware
|
7
7
|
extend Forwardable
|
8
8
|
|
9
9
|
def initialize(app, logger, options)
|
@@ -24,7 +24,11 @@ module Restforce
|
|
24
24
|
headers: env[:request_headers],
|
25
25
|
body: env[:body]
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
|
+
on_request(env) if respond_to?(:on_request)
|
29
|
+
@app.call(env).on_complete do |environment|
|
30
|
+
on_complete(environment) if respond_to?(:on_complete)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
def on_complete(env)
|
@@ -1,7 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Restforce
|
4
|
-
class Middleware::RaiseError < Faraday::
|
4
|
+
class Middleware::RaiseError < Faraday::Middleware
|
5
|
+
# Required for Faraday versions pre-v1.2.0 which do not include
|
6
|
+
# an implementation of `#call` by default
|
7
|
+
def call(env)
|
8
|
+
on_request(env) if respond_to?(:on_request)
|
9
|
+
@app.call(env).on_complete do |environment|
|
10
|
+
on_complete(environment) if respond_to?(:on_complete)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
5
14
|
def on_complete(env)
|
6
15
|
@env = env
|
7
16
|
case env[:status]
|
data/lib/restforce/version.rb
CHANGED
data/lib/restforce.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'faraday'
|
4
|
-
require '
|
4
|
+
require 'faraday/follow_redirects'
|
5
5
|
require 'json'
|
6
6
|
require 'jwt'
|
7
7
|
|
@@ -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
|
@@ -58,25 +59,28 @@ module Restforce
|
|
58
59
|
# Consumers of this library that rescue and handle Faraday::ClientError
|
59
60
|
# can continue to do so.
|
60
61
|
ResponseError = Class.new(Faraday::ClientError)
|
62
|
+
CompositeAPIError = Class.new(ResponseError)
|
61
63
|
MatchesMultipleError= Class.new(ResponseError)
|
62
64
|
EntityTooLargeError = Class.new(ResponseError)
|
63
65
|
|
64
66
|
require 'restforce/error_code'
|
67
|
+
require 'restforce/middleware/json_request'
|
68
|
+
require 'restforce/middleware/json_response'
|
65
69
|
|
66
70
|
class << self
|
67
71
|
# Alias for Restforce::Data::Client.new
|
68
72
|
#
|
69
73
|
# Shamelessly pulled from https://github.com/pengwynn/octokit/blob/master/lib/octokit.rb
|
70
|
-
def new(
|
71
|
-
data(
|
74
|
+
def new(...)
|
75
|
+
data(...)
|
72
76
|
end
|
73
77
|
|
74
|
-
def data(
|
75
|
-
Restforce::Data::Client.new(
|
78
|
+
def data(...)
|
79
|
+
Restforce::Data::Client.new(...)
|
76
80
|
end
|
77
81
|
|
78
|
-
def tooling(
|
79
|
-
Restforce::Tooling::Client.new(
|
82
|
+
def tooling(...)
|
83
|
+
Restforce::Tooling::Client.new(...)
|
80
84
|
end
|
81
85
|
|
82
86
|
# Helper for decoding signed requests.
|
data/restforce.gemspec
CHANGED
@@ -12,30 +12,22 @@ Gem::Specification.new do |gem|
|
|
12
12
|
|
13
13
|
gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
14
14
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
15
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
15
|
gem.name = "restforce"
|
17
16
|
gem.require_paths = ["lib"]
|
18
17
|
gem.version = Restforce::VERSION
|
19
18
|
|
20
19
|
gem.metadata = {
|
21
20
|
'source_code_uri' => 'https://github.com/restforce/restforce',
|
22
|
-
'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md'
|
21
|
+
'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md',
|
22
|
+
'rubygems_mfa_required' => 'true'
|
23
23
|
}
|
24
24
|
|
25
|
-
gem.required_ruby_version = '>= 2.
|
26
|
-
|
27
|
-
gem.add_dependency 'faraday', '<= 2.0', '>= 0.9.0'
|
28
|
-
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 2.0']
|
25
|
+
gem.required_ruby_version = '>= 2.7'
|
29
26
|
|
27
|
+
gem.add_dependency 'faraday', '< 2.8.0', '>= 1.1.0'
|
28
|
+
gem.add_dependency 'faraday-follow_redirects', '<= 0.3.0', '< 1.0.0'
|
29
|
+
gem.add_dependency 'faraday-multipart', '>= 1.0.0', '< 2.0.0'
|
30
|
+
gem.add_dependency 'faraday-net_http', '< 4.0.0'
|
31
|
+
gem.add_dependency 'hashie', '>= 1.2.0', '< 6.0'
|
30
32
|
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
33
|
end
|