restforce 5.0.6 → 6.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.
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