restforce 5.0.6 → 6.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +4 -13
  3. data/.github/funding.yml +1 -0
  4. data/.github/workflows/build.yml +23 -0
  5. data/.github/workflows/faraday.yml +27 -0
  6. data/.rubocop.yml +2 -2
  7. data/CHANGELOG.md +68 -0
  8. data/Gemfile +15 -6
  9. data/README.md +61 -7
  10. data/UPGRADING.md +29 -0
  11. data/lib/restforce/abstract_client.rb +1 -0
  12. data/lib/restforce/collection.rb +20 -2
  13. data/lib/restforce/concerns/api.rb +2 -1
  14. data/lib/restforce/concerns/base.rb +2 -2
  15. data/lib/restforce/concerns/composite_api.rb +104 -0
  16. data/lib/restforce/concerns/connection.rb +1 -1
  17. data/lib/restforce/concerns/picklists.rb +1 -1
  18. data/lib/restforce/config.rb +12 -10
  19. data/lib/restforce/error_code.rb +30 -9
  20. data/lib/restforce/file_part.rb +12 -4
  21. data/lib/restforce/middleware/authentication.rb +1 -0
  22. data/lib/restforce/middleware/caching.rb +140 -15
  23. data/lib/restforce/middleware/gzip.rb +4 -0
  24. data/lib/restforce/middleware/json_request.rb +90 -0
  25. data/lib/restforce/middleware/json_response.rb +85 -0
  26. data/lib/restforce/middleware/logger.rb +6 -2
  27. data/lib/restforce/middleware/raise_error.rb +10 -1
  28. data/lib/restforce/version.rb +1 -1
  29. data/lib/restforce.rb +11 -7
  30. data/restforce.gemspec +8 -16
  31. data/spec/fixtures/sobject/list_view_results_success_response.json +151 -0
  32. data/spec/integration/abstract_client_spec.rb +42 -30
  33. data/spec/integration/data/client_spec.rb +6 -2
  34. data/spec/spec_helper.rb +10 -0
  35. data/spec/support/client_integration.rb +7 -7
  36. data/spec/support/concerns.rb +1 -1
  37. data/spec/support/middleware.rb +1 -2
  38. data/spec/unit/collection_spec.rb +22 -4
  39. data/spec/unit/concerns/api_spec.rb +22 -15
  40. data/spec/unit/concerns/authentication_spec.rb +6 -6
  41. data/spec/unit/concerns/base_spec.rb +1 -1
  42. data/spec/unit/concerns/composite_api_spec.rb +169 -0
  43. data/spec/unit/concerns/connection_spec.rb +1 -1
  44. data/spec/unit/concerns/streaming_spec.rb +4 -4
  45. data/spec/unit/config_spec.rb +2 -2
  46. data/spec/unit/middleware/authentication/jwt_bearer_spec.rb +24 -8
  47. data/spec/unit/middleware/authentication/password_spec.rb +12 -4
  48. data/spec/unit/middleware/authentication/token_spec.rb +12 -4
  49. data/spec/unit/middleware/authentication_spec.rb +8 -8
  50. data/spec/unit/middleware/authorization_spec.rb +5 -1
  51. data/spec/unit/middleware/custom_headers_spec.rb +6 -2
  52. data/spec/unit/middleware/gzip_spec.rb +60 -16
  53. data/spec/unit/middleware/instance_url_spec.rb +2 -2
  54. data/spec/unit/middleware/logger_spec.rb +1 -1
  55. data/spec/unit/middleware/raise_error_spec.rb +20 -10
  56. data/spec/unit/sobject_spec.rb +9 -5
  57. metadata +55 -172
  58. data/.circleci/config.yml +0 -56
@@ -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
- "unhandled-salesforce-error.md&title=Unhandled+Salesforce+error%3A+%3Cinsert+" \
7
- "error+code+here%3E"
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
- "received from Salesforce. Instead of raising an error-specific exception, " \
610
- "we'll raise a generic `ResponseError`. Please report this missing error code" \
611
- " on GitHub at <#{GITHUB_ISSUE_URL}>."
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
- "`#{name}::#{constant_name}` (for example by trying to `rescue` it). This might " \
622
- "be our fault - we've recently made some changes to how errors are defined. If " \
623
- "you're sure that this is a valid Salesforce error, then please create an " \
624
- "issue on GitHub at <#{GITHUB_ISSUE_URL}>."
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
@@ -1,11 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
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
- rescue LoadError
6
- require 'faraday/upload_io'
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
- class Middleware::Caching < FaradayMiddleware::Caching
5
- def call(env)
6
- expire(cache_key(env)) unless use_cache?
7
- super
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 expire(key)
11
- cache&.delete(key)
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
- super(env) + hashed_auth_header(env)
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 use_cache?
21
- @options[:use_cache]
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 hashed_auth_header(env)
25
- Digest::SHA1.hexdigest(
26
- env[:request_headers][Restforce::Middleware::Authorization::AUTH_HEADER]
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::Response::Middleware
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
- super
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::Response::Middleware
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]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Restforce
4
- VERSION = '5.0.6'
4
+ VERSION = '6.2.1'
5
5
  end
data/lib/restforce.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'faraday'
4
- require 'faraday_middleware'
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(*args, &block)
71
- data(*args, &block)
74
+ def new(...)
75
+ data(...)
72
76
  end
73
77
 
74
- def data(*args, &block)
75
- Restforce::Data::Client.new(*args, &block)
78
+ def data(...)
79
+ Restforce::Data::Client.new(...)
76
80
  end
77
81
 
78
- def tooling(*args, &block)
79
- Restforce::Tooling::Client.new(*args, &block)
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.5'
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