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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +96 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +107 -0
- data/Guardfile +8 -0
- data/LICENSE +22 -0
- data/README.md +421 -0
- data/Rakefile +10 -0
- data/coverage/assets/0.7.1/application.css +1110 -0
- data/coverage/assets/0.7.1/application.js +626 -0
- data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
- data/coverage/assets/0.7.1/favicon_green.png +0 -0
- data/coverage/assets/0.7.1/favicon_red.png +0 -0
- data/coverage/assets/0.7.1/favicon_yellow.png +0 -0
- data/coverage/assets/0.7.1/loading.gif +0 -0
- data/coverage/assets/0.7.1/magnify.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/index.html +19808 -0
- data/force.gemspec +27 -0
- data/lib/force.rb +74 -0
- data/lib/force/abstract_client.rb +9 -0
- data/lib/force/attachment.rb +21 -0
- data/lib/force/client.rb +3 -0
- data/lib/force/collection.rb +45 -0
- data/lib/force/concerns/api.rb +321 -0
- data/lib/force/concerns/authentication.rb +39 -0
- data/lib/force/concerns/base.rb +59 -0
- data/lib/force/concerns/caching.rb +24 -0
- data/lib/force/concerns/canvas.rb +10 -0
- data/lib/force/concerns/connection.rb +74 -0
- data/lib/force/concerns/picklists.rb +87 -0
- data/lib/force/concerns/streaming.rb +31 -0
- data/lib/force/concerns/verbs.rb +67 -0
- data/lib/force/config.rb +140 -0
- data/lib/force/data/client.rb +18 -0
- data/lib/force/mash.rb +66 -0
- data/lib/force/middleware.rb +27 -0
- data/lib/force/middleware/authentication.rb +73 -0
- data/lib/force/middleware/authentication/password.rb +17 -0
- data/lib/force/middleware/authentication/token.rb +15 -0
- data/lib/force/middleware/authorization.rb +15 -0
- data/lib/force/middleware/caching.rb +22 -0
- data/lib/force/middleware/gzip.rb +31 -0
- data/lib/force/middleware/instance_url.rb +14 -0
- data/lib/force/middleware/logger.rb +40 -0
- data/lib/force/middleware/mashify.rb +16 -0
- data/lib/force/middleware/multipart.rb +55 -0
- data/lib/force/middleware/raise_error.rb +25 -0
- data/lib/force/signed_request.rb +48 -0
- data/lib/force/sobject.rb +68 -0
- data/lib/force/tooling/client.rb +11 -0
- data/lib/force/upload_io.rb +20 -0
- data/lib/force/version.rb +3 -0
- data/spec/fixtures/auth_error_response.json +4 -0
- data/spec/fixtures/auth_success_response.json +7 -0
- data/spec/fixtures/blob.jpg +0 -0
- data/spec/fixtures/expired_session_response.json +6 -0
- data/spec/fixtures/reauth_success_response.json +7 -0
- data/spec/fixtures/refresh_error_response.json +4 -0
- data/spec/fixtures/refresh_success_response.json +7 -0
- data/spec/fixtures/services_data_success_response.json +12 -0
- data/spec/fixtures/sobject/create_success_response.json +5 -0
- data/spec/fixtures/sobject/delete_error_response.json +1 -0
- data/spec/fixtures/sobject/describe_sobjects_success_response.json +31 -0
- data/spec/fixtures/sobject/list_sobjects_success_response.json +31 -0
- data/spec/fixtures/sobject/org_query_response.json +11 -0
- data/spec/fixtures/sobject/query_aggregate_success_response.json +23 -0
- data/spec/fixtures/sobject/query_empty_response.json +5 -0
- data/spec/fixtures/sobject/query_error_response.json +6 -0
- data/spec/fixtures/sobject/query_paginated_first_page_response.json +14 -0
- data/spec/fixtures/sobject/query_paginated_last_page_response.json +13 -0
- data/spec/fixtures/sobject/query_success_response.json +38 -0
- data/spec/fixtures/sobject/recent_success_response.json +18 -0
- data/spec/fixtures/sobject/search_error_response.json +6 -0
- data/spec/fixtures/sobject/search_success_response.json +16 -0
- data/spec/fixtures/sobject/sobject_describe_error_response.json +6 -0
- data/spec/fixtures/sobject/sobject_describe_success_response.json +1429 -0
- data/spec/fixtures/sobject/sobject_find_error_response.json +6 -0
- data/spec/fixtures/sobject/sobject_find_success_response.json +29 -0
- data/spec/fixtures/sobject/upsert_created_success_response.json +5 -0
- data/spec/fixtures/sobject/upsert_error_response.json +6 -0
- data/spec/fixtures/sobject/upsert_multiple_error_response.json +4 -0
- data/spec/fixtures/sobject/upsert_updated_success_response.json +0 -0
- data/spec/fixtures/sobject/write_error_response.json +6 -0
- data/spec/integration/abstract_client_spec.rb +306 -0
- data/spec/integration/data/client_spec.rb +90 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/client_integration.rb +45 -0
- data/spec/support/concerns.rb +18 -0
- data/spec/support/event_machine.rb +14 -0
- data/spec/support/fixture_helpers.rb +45 -0
- data/spec/support/matchers.rb +11 -0
- data/spec/support/middleware.rb +76 -0
- data/spec/support/mock_cache.rb +13 -0
- data/spec/unit/abstract_client_spec.rb +11 -0
- data/spec/unit/attachment_spec.rb +15 -0
- data/spec/unit/collection_spec.rb +52 -0
- data/spec/unit/concerns/api_spec.rb +244 -0
- data/spec/unit/concerns/authentication_spec.rb +98 -0
- data/spec/unit/concerns/base_spec.rb +42 -0
- data/spec/unit/concerns/caching_spec.rb +29 -0
- data/spec/unit/concerns/canvas_spec.rb +30 -0
- data/spec/unit/concerns/connection_spec.rb +22 -0
- data/spec/unit/config_spec.rb +99 -0
- data/spec/unit/data/client_spec.rb +10 -0
- data/spec/unit/mash_spec.rb +36 -0
- data/spec/unit/middleware/authentication/password_spec.rb +31 -0
- data/spec/unit/middleware/authentication/token_spec.rb +24 -0
- data/spec/unit/middleware/authentication_spec.rb +67 -0
- data/spec/unit/middleware/authorization_spec.rb +11 -0
- data/spec/unit/middleware/gzip_spec.rb +66 -0
- data/spec/unit/middleware/instance_url_spec.rb +24 -0
- data/spec/unit/middleware/logger_spec.rb +19 -0
- data/spec/unit/middleware/mashify_spec.rb +11 -0
- data/spec/unit/middleware/raise_error_spec.rb +32 -0
- data/spec/unit/signed_request_spec.rb +24 -0
- data/spec/unit/sobject_spec.rb +86 -0
- data/spec/unit/tooling/client_spec.rb +7 -0
- data/tmp/rspec_guard_result +1 -0
- 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
|