force 0.0.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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +96 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +107 -0
  5. data/Guardfile +8 -0
  6. data/LICENSE +22 -0
  7. data/README.md +421 -0
  8. data/Rakefile +10 -0
  9. data/coverage/assets/0.7.1/application.css +1110 -0
  10. data/coverage/assets/0.7.1/application.js +626 -0
  11. data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  12. data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  13. data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  14. data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  15. data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  16. data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  17. data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  18. data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  19. data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  20. data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  21. data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  22. data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  23. data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  24. data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  25. data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  26. data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  27. data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  28. data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  29. data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  30. data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  31. data/coverage/assets/0.7.1/favicon_green.png +0 -0
  32. data/coverage/assets/0.7.1/favicon_red.png +0 -0
  33. data/coverage/assets/0.7.1/favicon_yellow.png +0 -0
  34. data/coverage/assets/0.7.1/loading.gif +0 -0
  35. data/coverage/assets/0.7.1/magnify.png +0 -0
  36. data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  37. data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  38. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  39. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  40. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  41. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  42. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  43. data/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  44. data/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
  45. data/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  46. data/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
  47. data/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
  48. data/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  49. data/coverage/index.html +19808 -0
  50. data/force.gemspec +27 -0
  51. data/lib/force.rb +74 -0
  52. data/lib/force/abstract_client.rb +9 -0
  53. data/lib/force/attachment.rb +21 -0
  54. data/lib/force/client.rb +3 -0
  55. data/lib/force/collection.rb +45 -0
  56. data/lib/force/concerns/api.rb +321 -0
  57. data/lib/force/concerns/authentication.rb +39 -0
  58. data/lib/force/concerns/base.rb +59 -0
  59. data/lib/force/concerns/caching.rb +24 -0
  60. data/lib/force/concerns/canvas.rb +10 -0
  61. data/lib/force/concerns/connection.rb +74 -0
  62. data/lib/force/concerns/picklists.rb +87 -0
  63. data/lib/force/concerns/streaming.rb +31 -0
  64. data/lib/force/concerns/verbs.rb +67 -0
  65. data/lib/force/config.rb +140 -0
  66. data/lib/force/data/client.rb +18 -0
  67. data/lib/force/mash.rb +66 -0
  68. data/lib/force/middleware.rb +27 -0
  69. data/lib/force/middleware/authentication.rb +73 -0
  70. data/lib/force/middleware/authentication/password.rb +17 -0
  71. data/lib/force/middleware/authentication/token.rb +15 -0
  72. data/lib/force/middleware/authorization.rb +15 -0
  73. data/lib/force/middleware/caching.rb +22 -0
  74. data/lib/force/middleware/gzip.rb +31 -0
  75. data/lib/force/middleware/instance_url.rb +14 -0
  76. data/lib/force/middleware/logger.rb +40 -0
  77. data/lib/force/middleware/mashify.rb +16 -0
  78. data/lib/force/middleware/multipart.rb +55 -0
  79. data/lib/force/middleware/raise_error.rb +25 -0
  80. data/lib/force/signed_request.rb +48 -0
  81. data/lib/force/sobject.rb +68 -0
  82. data/lib/force/tooling/client.rb +11 -0
  83. data/lib/force/upload_io.rb +20 -0
  84. data/lib/force/version.rb +3 -0
  85. data/spec/fixtures/auth_error_response.json +4 -0
  86. data/spec/fixtures/auth_success_response.json +7 -0
  87. data/spec/fixtures/blob.jpg +0 -0
  88. data/spec/fixtures/expired_session_response.json +6 -0
  89. data/spec/fixtures/reauth_success_response.json +7 -0
  90. data/spec/fixtures/refresh_error_response.json +4 -0
  91. data/spec/fixtures/refresh_success_response.json +7 -0
  92. data/spec/fixtures/services_data_success_response.json +12 -0
  93. data/spec/fixtures/sobject/create_success_response.json +5 -0
  94. data/spec/fixtures/sobject/delete_error_response.json +1 -0
  95. data/spec/fixtures/sobject/describe_sobjects_success_response.json +31 -0
  96. data/spec/fixtures/sobject/list_sobjects_success_response.json +31 -0
  97. data/spec/fixtures/sobject/org_query_response.json +11 -0
  98. data/spec/fixtures/sobject/query_aggregate_success_response.json +23 -0
  99. data/spec/fixtures/sobject/query_empty_response.json +5 -0
  100. data/spec/fixtures/sobject/query_error_response.json +6 -0
  101. data/spec/fixtures/sobject/query_paginated_first_page_response.json +14 -0
  102. data/spec/fixtures/sobject/query_paginated_last_page_response.json +13 -0
  103. data/spec/fixtures/sobject/query_success_response.json +38 -0
  104. data/spec/fixtures/sobject/recent_success_response.json +18 -0
  105. data/spec/fixtures/sobject/search_error_response.json +6 -0
  106. data/spec/fixtures/sobject/search_success_response.json +16 -0
  107. data/spec/fixtures/sobject/sobject_describe_error_response.json +6 -0
  108. data/spec/fixtures/sobject/sobject_describe_success_response.json +1429 -0
  109. data/spec/fixtures/sobject/sobject_find_error_response.json +6 -0
  110. data/spec/fixtures/sobject/sobject_find_success_response.json +29 -0
  111. data/spec/fixtures/sobject/upsert_created_success_response.json +5 -0
  112. data/spec/fixtures/sobject/upsert_error_response.json +6 -0
  113. data/spec/fixtures/sobject/upsert_multiple_error_response.json +4 -0
  114. data/spec/fixtures/sobject/upsert_updated_success_response.json +0 -0
  115. data/spec/fixtures/sobject/write_error_response.json +6 -0
  116. data/spec/integration/abstract_client_spec.rb +306 -0
  117. data/spec/integration/data/client_spec.rb +90 -0
  118. data/spec/spec_helper.rb +20 -0
  119. data/spec/support/client_integration.rb +45 -0
  120. data/spec/support/concerns.rb +18 -0
  121. data/spec/support/event_machine.rb +14 -0
  122. data/spec/support/fixture_helpers.rb +45 -0
  123. data/spec/support/matchers.rb +11 -0
  124. data/spec/support/middleware.rb +76 -0
  125. data/spec/support/mock_cache.rb +13 -0
  126. data/spec/unit/abstract_client_spec.rb +11 -0
  127. data/spec/unit/attachment_spec.rb +15 -0
  128. data/spec/unit/collection_spec.rb +52 -0
  129. data/spec/unit/concerns/api_spec.rb +244 -0
  130. data/spec/unit/concerns/authentication_spec.rb +98 -0
  131. data/spec/unit/concerns/base_spec.rb +42 -0
  132. data/spec/unit/concerns/caching_spec.rb +29 -0
  133. data/spec/unit/concerns/canvas_spec.rb +30 -0
  134. data/spec/unit/concerns/connection_spec.rb +22 -0
  135. data/spec/unit/config_spec.rb +99 -0
  136. data/spec/unit/data/client_spec.rb +10 -0
  137. data/spec/unit/mash_spec.rb +36 -0
  138. data/spec/unit/middleware/authentication/password_spec.rb +31 -0
  139. data/spec/unit/middleware/authentication/token_spec.rb +24 -0
  140. data/spec/unit/middleware/authentication_spec.rb +67 -0
  141. data/spec/unit/middleware/authorization_spec.rb +11 -0
  142. data/spec/unit/middleware/gzip_spec.rb +66 -0
  143. data/spec/unit/middleware/instance_url_spec.rb +24 -0
  144. data/spec/unit/middleware/logger_spec.rb +19 -0
  145. data/spec/unit/middleware/mashify_spec.rb +11 -0
  146. data/spec/unit/middleware/raise_error_spec.rb +32 -0
  147. data/spec/unit/signed_request_spec.rb +24 -0
  148. data/spec/unit/sobject_spec.rb +86 -0
  149. data/spec/unit/tooling/client_spec.rb +7 -0
  150. data/tmp/rspec_guard_result +1 -0
  151. metadata +383 -0
@@ -0,0 +1,18 @@
1
+ module Force
2
+ module Data
3
+ class Client < AbstractClient
4
+ include Force::Concerns::Streaming
5
+ include Force::Concerns::Picklists
6
+ include Force::Concerns::Canvas
7
+
8
+ # Public: Returns a url to the resource.
9
+ #
10
+ # resource - A record that responds to to_sparam or a String/Fixnum.
11
+ #
12
+ # Returns the url to the resource.
13
+ def url(resource)
14
+ "#{instance_url}/#{(resource.respond_to?(:to_sparam) ? resource.to_sparam : resource)}"
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/force/mash.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'hashie/mash'
2
+
3
+ module Force
4
+ class Mash < Hashie::Mash
5
+ class << self
6
+ # Pass in an Array or Hash and it will be recursively converted into the
7
+ # appropriate Force::Collection, Force::SObject and
8
+ # Force::Mash objects.
9
+ def build(val, client)
10
+ if val.is_a?(Array)
11
+ val.collect { |val| self.build(val, client) }
12
+ elsif val.is_a?(Hash)
13
+ self.klass(val).new(val, client)
14
+ else
15
+ val
16
+ end
17
+ end
18
+
19
+ # When passed a hash, it will determine what class is appropriate to
20
+ # represent the data.
21
+ def klass(val)
22
+ if val.has_key? 'records'
23
+ # When the hash has a records key, it should be considered a collection
24
+ # of sobject records.
25
+ Force::Collection
26
+ elsif val.has_key? 'attributes'
27
+ if val['attributes']['type'] == 'Attachment'
28
+ Force::Attachment
29
+ else
30
+ # When the hash contains an attributes key, it should be considered an
31
+ # sobject record
32
+ Force::SObject
33
+ end
34
+ else
35
+ # Fallback to a standard Force::Mash for everything else
36
+ Force::Mash
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ def initialize(source_hash = nil, client = nil, default = nil, &blk)
43
+ @client = client
44
+ deep_update(source_hash) if source_hash
45
+ default ? super(default) : super(&blk)
46
+ end
47
+
48
+ def dup
49
+ self.class.new(self, @client, self.default)
50
+ end
51
+
52
+ def convert_value(val, duping=false)
53
+ case val
54
+ when self.class
55
+ val.dup
56
+ when ::Hash
57
+ val = val.dup if duping
58
+ self.class.klass(val).new(val, @client)
59
+ when Array
60
+ val.collect{ |e| convert_value(e) }
61
+ else
62
+ val
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,27 @@
1
+ module Force
2
+ class Middleware < Faraday::Middleware
3
+ autoload :RaiseError, 'force/middleware/raise_error'
4
+ autoload :Authentication, 'force/middleware/authentication'
5
+ autoload :Authorization, 'force/middleware/authorization'
6
+ autoload :InstanceURL, 'force/middleware/instance_url'
7
+ autoload :Multipart, 'force/middleware/multipart'
8
+ autoload :Mashify, 'force/middleware/mashify'
9
+ autoload :Caching, 'force/middleware/caching'
10
+ autoload :Logger, 'force/middleware/logger'
11
+ autoload :Gzip, 'force/middleware/gzip'
12
+
13
+ def initialize(app, client, options)
14
+ @app, @client, @options = app, client, options
15
+ end
16
+
17
+ # Internal: Proxy to the client.
18
+ def client
19
+ @client
20
+ end
21
+
22
+ # Internal: Proxy to the client's faraday connection.
23
+ def connection
24
+ client.send(:connection)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,73 @@
1
+ module Force
2
+ # Faraday middleware that allows for on the fly authentication of requests.
3
+ # When a request fails (a status of 401 is returned), the middleware
4
+ # will attempt to either reauthenticate (username and password) or refresh
5
+ # the oauth access token (if a refresh token is present).
6
+ class Middleware::Authentication < Force::Middleware
7
+ autoload :Password, 'force/middleware/authentication/password'
8
+ autoload :Token, 'force/middleware/authentication/token'
9
+
10
+ # Rescue from 401's, authenticate then raise the error again so the client
11
+ # can reissue the request.
12
+ def call(env)
13
+ @app.call(env)
14
+ rescue Force::UnauthorizedError
15
+ authenticate!
16
+ raise
17
+ end
18
+
19
+ # Internal: Performs the authentication and returns the response body.
20
+ def authenticate!
21
+ response = connection.post '/services/oauth2/token' do |req|
22
+ req.body = encode_www_form(params)
23
+ end
24
+ raise Force::AuthenticationError, error_message(response) if response.status != 200
25
+ @options[:instance_url] = response.body['instance_url']
26
+ @options[:oauth_token] = response.body['access_token']
27
+ @options[:authentication_callback].call(response.body) if @options[:authentication_callback]
28
+ response.body
29
+ end
30
+
31
+ # Internal: The params to post to the OAuth service.
32
+ def params
33
+ raise NotImplementedError
34
+ end
35
+
36
+ # Internal: Faraday connection to use when sending an authentication request.
37
+ def connection
38
+ @connection ||= Faraday.new(faraday_options) do |builder|
39
+ builder.use Faraday::Request::UrlEncoded
40
+ builder.use Force::Middleware::Mashify, nil, @options
41
+ builder.response :json
42
+ builder.use Force::Middleware::Logger, Force.configuration.logger, @options if Force.log?
43
+ builder.adapter Faraday.default_adapter
44
+ end
45
+ end
46
+
47
+ # Internal: The parsed error response.
48
+ def error_message(response)
49
+ "#{response.body['error']}: #{response.body['error_description']}"
50
+ end
51
+
52
+ # Featured detect form encoding.
53
+ # URI in 1.8 does not include encode_www_form
54
+ def encode_www_form(params)
55
+ if URI.respond_to?(:encode_www_form)
56
+ URI.encode_www_form(params)
57
+ else
58
+ params.map do |k, v|
59
+ k, v = CGI.escape(k.to_s), CGI.escape(v.to_s)
60
+ "#{k}=#{v}"
61
+ end.join('&')
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def faraday_options
68
+ { :url => "https://#{@options[:host]}",
69
+ :proxy => @options[:proxy_uri]
70
+ }.reject { |k, v| v.nil? }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,17 @@
1
+ module Force
2
+ # Authentication middleware used if username and password flow is used
3
+ class Middleware::Authentication::Password < Force::Middleware::Authentication
4
+ def params
5
+ { :grant_type => 'password',
6
+ :client_id => @options[:client_id],
7
+ :client_secret => @options[:client_secret],
8
+ :username => @options[:username],
9
+ :password => password
10
+ }
11
+ end
12
+
13
+ def password
14
+ "#{@options[:password]}#{@options[:security_token]}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Force
2
+
3
+ # Authentication middleware used if oauth_token and refresh_token are set
4
+ class Middleware::Authentication::Token < Force::Middleware::Authentication
5
+
6
+ def params
7
+ { :grant_type => 'refresh_token',
8
+ :refresh_token => @options[:refresh_token],
9
+ :client_id => @options[:client_id],
10
+ :client_secret => @options[:client_secret] }
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,15 @@
1
+ module Force
2
+ # Middleware that simply injects the OAuth token into the request headers.
3
+ class Middleware::Authorization < Force::Middleware
4
+ AUTH_HEADER = 'Authorization'.freeze
5
+
6
+ def call(env)
7
+ env[:request_headers][AUTH_HEADER] = %(OAuth #{token})
8
+ @app.call(env)
9
+ end
10
+
11
+ def token
12
+ @options[:oauth_token]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ module Force
2
+ class Middleware::Caching < FaradayMiddleware::Caching
3
+ def call(env)
4
+ expire(cache_key(env)) unless use_cache?
5
+ super
6
+ end
7
+
8
+ def expire(key)
9
+ cache.delete(key) if cache
10
+ end
11
+
12
+ # We don't want to cache requests for different clients, so append the
13
+ # oauth token to the cache key.
14
+ def cache_key(env)
15
+ super(env) + env[:request_headers][Force::Middleware::Authorization::AUTH_HEADER].gsub(/\s/, '')
16
+ end
17
+
18
+ def use_cache?
19
+ !@options.has_key?(:use_cache) || @options[:use_cache]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ require 'zlib'
2
+
3
+ module Force
4
+ # Middleware to uncompress GZIP compressed responses from Salesforce.
5
+ class Middleware::Gzip < Force::Middleware
6
+ ACCEPT_ENCODING_HEADER = 'Accept-Encoding'.freeze
7
+ CONTENT_ENCODING_HEADER = 'Content-Encoding'.freeze
8
+ ENCODING = 'gzip'.freeze
9
+
10
+ def call(env)
11
+ env[:request_headers][ACCEPT_ENCODING_HEADER] = ENCODING if @options[:compress]
12
+ @app.call(env).on_complete do |environment|
13
+ on_complete(environment)
14
+ end
15
+ end
16
+
17
+ def on_complete(env)
18
+ env[:body] = decompress(env[:body]) if gzipped?(env)
19
+ end
20
+
21
+ # Internal: Returns true if the response is gzipped.
22
+ def gzipped?(env)
23
+ env[:response_headers][CONTENT_ENCODING_HEADER] == ENCODING
24
+ end
25
+
26
+ # Internal: Decompresses a gzipped string.
27
+ def decompress(body)
28
+ Zlib::GzipReader.new(StringIO.new(body)).read
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module Force
2
+ # Middleware which asserts that the instance_url is always set
3
+ class Middleware::InstanceURL < Force::Middleware
4
+ def call(env)
5
+ # If the connection url_prefix isn't set, we must not be authenticated.
6
+ raise Force::UnauthorizedError, 'Connection prefix not set' unless url_prefix_set?
7
+ @app.call(env)
8
+ end
9
+
10
+ def url_prefix_set?
11
+ !!(connection.url_prefix && connection.url_prefix.host)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ require 'forwardable'
2
+
3
+ module Force
4
+ class Middleware::Logger < Faraday::Response::Middleware
5
+ extend Forwardable
6
+
7
+ def initialize(app, logger, options)
8
+ super(app)
9
+ @options = options
10
+ @logger = logger || begin
11
+ require 'logger'
12
+ ::Logger.new(STDOUT)
13
+ end
14
+ end
15
+
16
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
17
+
18
+ def call(env)
19
+ debug('request') do
20
+ dump :url => env[:url].to_s,
21
+ :method => env[:method],
22
+ :headers => env[:request_headers],
23
+ :body => env[:body]
24
+ end
25
+ super
26
+ end
27
+
28
+ def on_complete(env)
29
+ debug('response') do
30
+ dump :status => env[:status].to_s,
31
+ :headers => env[:response_headers],
32
+ :body => env[:body]
33
+ end
34
+ end
35
+
36
+ def dump(hash)
37
+ "\n" + hash.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ module Force
2
+ # Middleware the converts sobject records from JSON into Force::SObject objects
3
+ # and collections of records into Force::Collection objects.
4
+ class Middleware::Mashify < Force::Middleware
5
+ def call(env)
6
+ @env = env
7
+ response = @app.call(env)
8
+ env[:body] = Force::Mash.build(body, client)
9
+ response
10
+ end
11
+
12
+ def body
13
+ @env[:body]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,55 @@
1
+ module Force
2
+ class Middleware::Multipart < Faraday::Request::UrlEncoded
3
+ self.mime_type = 'multipart/form-data'.freeze
4
+ DEFAULT_BOUNDARY = "--boundary_string".freeze
5
+
6
+ def call(env)
7
+ match_content_type(env) do |params|
8
+ env[:request] ||= {}
9
+ env[:request][:boundary] ||= DEFAULT_BOUNDARY
10
+ env[:request_headers][CONTENT_TYPE] += ";boundary=#{env[:request][:boundary]}"
11
+ env[:body] = create_multipart(env, params)
12
+ end
13
+
14
+ @app.call(env)
15
+ end
16
+
17
+ def process_request?(env)
18
+ type = request_type(env)
19
+ env[:body].respond_to?(:each_key) and !env[:body].empty? and (
20
+ (type.empty? and has_multipart?(env[:body])) or
21
+ type == self.class.mime_type
22
+ )
23
+ end
24
+
25
+ def has_multipart?(obj)
26
+ # string is an enum in 1.8, returning list of itself
27
+ if obj.respond_to?(:each) && !obj.is_a?(String)
28
+ (obj.respond_to?(:values) ? obj.values : obj).each do |val|
29
+ return true if (val.respond_to?(:content_type) || has_multipart?(val))
30
+ end
31
+ end
32
+
33
+ return false
34
+ end
35
+
36
+ def create_multipart(env, params)
37
+ boundary = env[:request][:boundary]
38
+
39
+ parts = []
40
+
41
+ parts << Faraday::Parts::Part.new(boundary, 'entity_content', params.reject { |k,v| v.respond_to? :content_type }.to_json)
42
+
43
+ params.each do |k,v|
44
+ parts << Faraday::Parts::Part.new(boundary, k.to_s, v) if v.respond_to? :content_type
45
+ end
46
+
47
+ parts << Faraday::Parts::EpiloguePart.new(boundary)
48
+
49
+ body = Faraday::CompositeReadIO.new(parts)
50
+ env[:request_headers]['Content-Length'] = body.length.to_s
51
+
52
+ return body
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,25 @@
1
+ module Force
2
+ class Middleware::RaiseError < Faraday::Response::Middleware
3
+ def on_complete(env)
4
+ @env = env
5
+ case env[:status]
6
+ when 404
7
+ raise Faraday::Error::ResourceNotFound, message
8
+ when 401
9
+ raise Force::UnauthorizedError, message
10
+ when 413
11
+ raise Faraday::Error::ClientError.new("HTTP 413 - Request Entity Too Large", env[:response])
12
+ when 400...600
13
+ raise Faraday::Error::ClientError.new(message, env[:response])
14
+ end
15
+ end
16
+
17
+ def message
18
+ "#{body.first['errorCode']}: #{body.first['message']}"
19
+ end
20
+
21
+ def body
22
+ JSON.parse(@env[:body])
23
+ end
24
+ end
25
+ end