saddle 0.0.12 → 0.0.14
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 +8 -8
- data/lib/saddle/client_attributes.rb +12 -18
- data/lib/saddle/endpoint.rb +0 -4
- data/lib/saddle/errors.rb +3 -1
- data/lib/saddle/method_tree_builder.rb +76 -73
- data/lib/saddle/middleware/logging/airbrake.rb +23 -21
- data/lib/saddle/middleware/logging/statsd.rb +36 -34
- data/lib/saddle/middleware/request/encode_json.rb +23 -21
- data/lib/saddle/middleware/request/retry.rb +42 -40
- data/lib/saddle/middleware/request/url_encoded.rb +59 -57
- data/lib/saddle/middleware/response/default_response.rb +17 -14
- data/lib/saddle/middleware/response/parse_json.rb +32 -30
- data/lib/saddle/middleware/ruby_timeout.rb +19 -14
- data/lib/saddle/options.rb +56 -58
- data/lib/saddle/requester.rb +0 -2
- data/lib/saddle/version.rb +1 -1
- data/lib/saddle.rb +11 -0
- data/spec/requester/multiple_spec.rb +73 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MDE2NjI0YTFjNzExNzNhNTVjZGIxNDc0MjBmNjQ4ZWUzNzA1ODFkMA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZDkyZTUxZjgwNjMyNmFjYmEwMDc5ZWNkOTZiYTU3ZjZlYzI2NzVkZA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
N2Y2ZDFiMmNhZjNmNmM5NzYxNzIzZWQ1YThmMGUzMTdkNTM3ZDM5YzJkNWJm
|
10
|
+
Y2Q5MGFkYzM0MGQ1NzdlNGY1ZmExMjFhN2JiZGNlZjRkMTIxZjQ0ZTUxMzNk
|
11
|
+
YWIxNWMxNTQ1YWQ4NjdkM2EzNDVjNTFkYjk3NjRiMmMxYmU5NWU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
Yzc0MzU4M2E5ZmQzYTQ5NTNlNjFlYzdjODJiYzgwZWNkOGRmZjg0OTYzMTU3
|
14
|
+
ZWZkZmUwZjMyMTBhZmI3OWY1YzdiNWQyYWY4NzkwNjk5Nzc3MzQ1ZDI2MTNm
|
15
|
+
NDdhYmRmZWI1YWQ0OTE3OWY5NTk1Yzg5NjRkYWQ2ZGE0MmE2YjU=
|
@@ -1,24 +1,18 @@
|
|
1
|
-
module Saddle
|
1
|
+
module Saddle
|
2
|
+
module ClientAttributes
|
2
3
|
|
3
|
-
|
4
|
-
|
4
|
+
def self.included(obj)
|
5
|
+
obj.extend ClassMethods
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
[]
|
7
|
+
# We know that this module is included when saddle client is inherited,
|
8
|
+
# so we're actually interested in the path of the caller two levels deep.
|
9
|
+
path, = caller[2].partition(":")
|
10
|
+
obj.implementation_root = File.dirname(path)
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
obj.implementation_root = File.dirname(path)
|
17
|
-
end
|
13
|
+
module ClassMethods
|
14
|
+
attr_accessor :implementation_root
|
15
|
+
end
|
18
16
|
|
19
|
-
module ClassMethods
|
20
|
-
attr_accessor :implementation_root
|
21
|
-
attr_accessor :additional_middlewares
|
22
17
|
end
|
23
|
-
|
24
|
-
end
|
18
|
+
end
|
data/lib/saddle/endpoint.rb
CHANGED
data/lib/saddle/errors.rb
CHANGED
@@ -10,94 +10,97 @@ require 'saddle/endpoint'
|
|
10
10
|
# build the endpoint tree based upon module/class namespaces
|
11
11
|
|
12
12
|
|
13
|
-
module Saddle
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
13
|
+
module Saddle
|
14
|
+
module MethodTreeBuilder
|
15
|
+
|
16
|
+
# Build out the endpoint structure from the root of the implementation
|
17
|
+
def build_tree(requester)
|
18
|
+
root_node = build_root_node(requester)
|
19
|
+
# If we have an 'endpoints' directory, build it out
|
20
|
+
if knows_root? && Dir.exists?(endpoints_directory)
|
21
|
+
Dir["#{endpoints_directory}/**/*.rb"].each { |f| load(f) }
|
22
|
+
build_node_children(self.endpoints_module, root_node, requester)
|
23
|
+
end
|
24
|
+
root_node
|
22
25
|
end
|
23
|
-
root_node
|
24
|
-
end
|
25
26
|
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
28
|
+
# Build our root node here. The root node is special in that it lives below
|
29
|
+
# the 'endpoints' directory, and so we need to manually check if it exists.
|
30
|
+
def build_root_node(requester)
|
31
|
+
if knows_root?
|
32
|
+
root_endpoint_file = File.join(
|
33
|
+
self.implementation_root,
|
34
|
+
'root_endpoint.rb'
|
35
|
+
)
|
36
|
+
if File.file?(root_endpoint_file)
|
37
|
+
# Load it and create our base endpoint
|
38
|
+
load(root_endpoint_file)
|
39
|
+
self.implementation_module::RootEndpoint.new(requester)
|
40
|
+
else
|
41
|
+
# 'root_endpoint.rb' doesn't exist, so create a dummy endpoint
|
42
|
+
Saddle::BaseEndpoint.new(requester)
|
43
|
+
end
|
39
44
|
else
|
40
|
-
#
|
45
|
+
# we don't even have an implementation root, so create a dummy endpoint
|
41
46
|
Saddle::BaseEndpoint.new(requester)
|
42
47
|
end
|
43
|
-
else
|
44
|
-
# we don't even have an implementation root, so create a dummy endpoint
|
45
|
-
Saddle::BaseEndpoint.new(requester)
|
46
48
|
end
|
47
|
-
end
|
48
49
|
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
51
|
+
# Build out the traversal tree by module namespace
|
52
|
+
def build_node_children(current_module, current_node, requester)
|
53
|
+
current_module.constants.each do |const_symbol|
|
54
|
+
const = current_module.const_get(const_symbol)
|
55
|
+
|
56
|
+
if const.class == Module
|
57
|
+
# A module means that it's a branch
|
58
|
+
# Build the branch out with a base endpoint
|
59
|
+
branch_node = current_node.build_and_attach_node(
|
60
|
+
Saddle::BaseEndpoint,
|
61
|
+
ActiveSupport::Inflector.underscore(const_symbol)
|
62
|
+
)
|
63
|
+
# Build out the branch's endpoints on the new branch node
|
64
|
+
self.build_node_children(const, branch_node, requester)
|
65
|
+
end
|
66
|
+
|
67
|
+
if const < Saddle::TraversalEndpoint
|
68
|
+
# A class means that it's a node
|
69
|
+
# Build out this endpoint on the current node
|
70
|
+
current_node.build_and_attach_node(
|
71
|
+
const,
|
72
|
+
ActiveSupport::Inflector.underscore(const_symbol)
|
73
|
+
)
|
74
|
+
end
|
73
75
|
end
|
74
76
|
end
|
75
|
-
end
|
76
77
|
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
79
|
+
# Get the module that the client implementation belongs to. This will act
|
80
|
+
# as the root namespace for endpoint traversal and construction
|
81
|
+
def implementation_module
|
82
|
+
::ActiveSupport::Inflector.constantize(
|
83
|
+
self.name.split('::')[0..-2].join('::')
|
84
|
+
)
|
85
|
+
end
|
85
86
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
87
|
+
# Get the Endpoints module that lives within this implementation's
|
88
|
+
# namespace
|
89
|
+
def endpoints_module
|
90
|
+
implementation_module.const_get('Endpoints')
|
91
|
+
end
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
93
|
+
# Get the path to the 'endpoints' directory, based upon the client
|
94
|
+
# class that inherited Saddle
|
95
|
+
def endpoints_directory
|
96
|
+
File.join(self.implementation_root, 'endpoints')
|
97
|
+
end
|
98
|
+
|
99
|
+
# If this client was not fully constructed, it may not even have an
|
100
|
+
# implementation root. Allow that behavior and avoid firesystem searching.
|
101
|
+
def knows_root?
|
102
|
+
defined?(self.implementation_root)
|
103
|
+
end
|
97
104
|
|
98
|
-
# If this client was not fully constructed, it may not even have an
|
99
|
-
# implementation root. Allow that behavior and avoid firesystem searching.
|
100
|
-
def knows_root?
|
101
|
-
defined?(self.implementation_root)
|
102
105
|
end
|
103
106
|
end
|
@@ -3,33 +3,35 @@ require 'faraday'
|
|
3
3
|
|
4
4
|
|
5
5
|
|
6
|
-
module Saddle
|
7
|
-
module
|
6
|
+
module Saddle
|
7
|
+
module Middleware
|
8
|
+
module Logging
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
# Public: Reports exceptions to Airbrake
|
11
|
+
#
|
12
|
+
class AirbrakeLogger < Faraday::Middleware
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def initialize(app, airbrake_api_key=nil)
|
15
|
+
super(app)
|
16
|
+
@airbrake_api_key = airbrake_api_key
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
def call(env)
|
20
|
+
begin
|
21
|
+
@app.call(env)
|
22
|
+
rescue => e
|
23
|
+
# If we don't have an api key, use the default config
|
24
|
+
if @airbrake_api_key
|
25
|
+
::Airbrake.notify(e, {:api_key => @airbrake_api_key} )
|
26
|
+
else
|
27
|
+
::Airbrake.notify(e)
|
28
|
+
end
|
29
|
+
# Re-raise the error
|
30
|
+
raise
|
27
31
|
end
|
28
|
-
# Re-raise the error
|
29
|
-
raise
|
30
32
|
end
|
31
|
-
end
|
32
33
|
|
34
|
+
end
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
@@ -4,48 +4,50 @@ require 'faraday'
|
|
4
4
|
|
5
5
|
|
6
6
|
|
7
|
-
module Saddle
|
8
|
-
module
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def statsd
|
24
|
-
@statsd ||= begin
|
25
|
-
client = ::Statsd.new(@graphite_host, @graphite_port)
|
26
|
-
client.namespace = @namespace if @namespace
|
7
|
+
module Saddle
|
8
|
+
module Middleware
|
9
|
+
module Logging
|
10
|
+
|
11
|
+
# Public: Wraps request with statsd logging
|
12
|
+
# Expects statsd_path in request options. However, if using saddle and no statsd_path is specified
|
13
|
+
# will read call_chain and action and use them to construct a statsd_path
|
14
|
+
class StatsdLogger < Faraday::Middleware
|
15
|
+
attr_accessor :graphite_host, :graphite_port, :namespace
|
16
|
+
|
17
|
+
def initialize(app, graphite_host, graphite_port=nil, namespace=nil)
|
18
|
+
super(app)
|
19
|
+
@graphite_host = graphite_host
|
20
|
+
@graphite_port = graphite_port
|
21
|
+
@namespace = namespace
|
27
22
|
end
|
28
|
-
end
|
29
23
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
elsif env[:request][:saddle] && env[:request][:saddle][:call_chain] && env[:request][:saddle][:action]
|
36
|
-
statsd_path = (['saddle'] + env[:request][:saddle][:call_chain] + [env[:request][:saddle][:action]]).join('.')
|
24
|
+
def statsd
|
25
|
+
@statsd ||= begin
|
26
|
+
client = ::Statsd.new(@graphite_host, @graphite_port)
|
27
|
+
client.namespace = @namespace if @namespace
|
28
|
+
end
|
37
29
|
end
|
38
30
|
|
39
|
-
|
40
|
-
|
41
|
-
|
31
|
+
def call(env)
|
32
|
+
# Try to build up a path for the STATSD logging
|
33
|
+
statsd_path = nil
|
34
|
+
if env[:request][:statsd_path]
|
35
|
+
statsd_path = env[:request][:statsd_path]
|
36
|
+
elsif env[:request][:saddle] && env[:request][:saddle][:call_chain] && env[:request][:saddle][:action]
|
37
|
+
statsd_path = (['saddle'] + env[:request][:saddle][:call_chain] + [env[:request][:saddle][:action]]).join('.')
|
38
|
+
end
|
39
|
+
|
40
|
+
# If we have a path, wrap the ensuing app call in STATSD timing
|
41
|
+
if statsd_path
|
42
|
+
self.statsd.time(statsd_path) do
|
43
|
+
@app.call(env)
|
44
|
+
end
|
45
|
+
else
|
42
46
|
@app.call(env)
|
43
47
|
end
|
44
|
-
else
|
45
|
-
@app.call(env)
|
46
48
|
end
|
47
49
|
end
|
48
|
-
end
|
49
50
|
|
51
|
+
end
|
50
52
|
end
|
51
53
|
end
|
@@ -2,33 +2,35 @@ require 'faraday'
|
|
2
2
|
|
3
3
|
|
4
4
|
|
5
|
-
module Saddle
|
6
|
-
module
|
5
|
+
module Saddle
|
6
|
+
module Middleware
|
7
|
+
module Request
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
# Request middleware that encodes the body as JSON.
|
10
|
+
#
|
11
|
+
# Make sure you set request[:request_style] = :json
|
12
|
+
# for it to be activated.
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
class JsonEncoded < Faraday::Middleware
|
15
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
16
|
+
MIME_TYPE = 'application/json'.freeze
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
dependency do
|
19
|
+
require 'json' unless defined?(::JSON)
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
def call(env)
|
23
|
+
if env[:request][:request_style] == :json
|
24
|
+
# Make sure we're working with a valid body that's not a String
|
25
|
+
if env[:body] and !env[:body].respond_to?(:to_str)
|
26
|
+
env[:request_headers][CONTENT_TYPE] ||= MIME_TYPE
|
27
|
+
env[:body] = ::JSON.dump(env[:body])
|
28
|
+
end
|
27
29
|
end
|
30
|
+
@app.call env
|
28
31
|
end
|
29
|
-
@app.call env
|
30
32
|
end
|
31
|
-
end
|
32
33
|
|
34
|
+
end
|
33
35
|
end
|
34
|
-
end
|
36
|
+
end
|
@@ -2,54 +2,56 @@ require 'faraday'
|
|
2
2
|
|
3
3
|
|
4
4
|
|
5
|
-
module Saddle
|
6
|
-
module
|
5
|
+
module Saddle
|
6
|
+
module Middleware
|
7
|
+
module Request
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
# Catches exceptions and retries each request a limited number of times.
|
10
|
+
#
|
11
|
+
# By default, it retries 2 times and performs exponential backoff, starting
|
12
|
+
# at 50ms
|
13
|
+
class Retry < Faraday::Middleware
|
14
|
+
def initialize(app, ignored_exceptions=[])
|
15
|
+
super(app)
|
16
|
+
@ignored_exceptions = ignored_exceptions
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
19
|
+
def call(env)
|
20
|
+
retries = env[:request][:num_retries] || 2
|
21
|
+
backoff = env[:request][:retry_backoff] || 0.050 # ms
|
22
|
+
begin
|
23
|
+
@app.call(self.class.deep_copy(env))
|
24
|
+
rescue => e
|
25
|
+
unless @ignored_exceptions.include?(e.class)
|
26
|
+
# Retry a limited number of times
|
27
|
+
if retries > 0
|
28
|
+
retries -= 1
|
29
|
+
sleep(backoff) if backoff > 0.0
|
30
|
+
backoff *= 2
|
31
|
+
retry
|
32
|
+
end
|
31
33
|
end
|
34
|
+
# Re-raise if we're out of retries or it's not handled
|
35
|
+
raise
|
32
36
|
end
|
33
|
-
# Re-raise if we're out of retries or it's not handled
|
34
|
-
raise
|
35
37
|
end
|
36
|
-
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
39
|
+
def self.deep_copy(value)
|
40
|
+
if value.is_a?(Hash)
|
41
|
+
result = value.clone
|
42
|
+
value.each{|k, v| result[k] = deep_copy(v)}
|
43
|
+
result
|
44
|
+
elsif value.is_a?(Array)
|
45
|
+
result = value.clone
|
46
|
+
result.clear
|
47
|
+
value.each{|v| result << deep_copy(v)}
|
48
|
+
result
|
49
|
+
else
|
50
|
+
value
|
51
|
+
end
|
50
52
|
end
|
51
53
|
end
|
52
|
-
end
|
53
54
|
|
55
|
+
end
|
54
56
|
end
|
55
57
|
end
|
@@ -2,85 +2,87 @@ require 'faraday'
|
|
2
2
|
|
3
3
|
|
4
4
|
|
5
|
-
module Saddle
|
6
|
-
module
|
5
|
+
module Saddle
|
6
|
+
module Middleware
|
7
|
+
module Request
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
# This magically handles converting your body from a hash
|
10
|
+
# into an url-encoded (or multipart if needed) request
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
# Make sure you set request[:request_style] = :urlencoded
|
13
|
+
# for it to be activated.
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
class UrlEncoded < Faraday::Middleware
|
16
|
+
CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
URL_ENCODED_MIME_TYPE = 'application/x-www-form-urlencoded'.freeze
|
19
|
+
MULTIPART_MIME_TYPE = 'multipart/form-data'.freeze
|
19
20
|
|
20
|
-
|
21
|
+
VALID_MIME_TYPES = [URL_ENCODED_MIME_TYPE, MULTIPART_MIME_TYPE]
|
21
22
|
|
22
|
-
|
23
|
+
DEFAULT_MULTIPART_BOUNDARY = "-^---_---^-".freeze
|
23
24
|
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
def call(env)
|
27
|
+
if env[:request][:request_style] == :urlencoded
|
28
|
+
# Make sure we're working with a valid body that's not a String
|
29
|
+
if env[:body] and !env[:body].respond_to?(:to_str)
|
30
|
+
if has_multipart?(env[:body])
|
31
|
+
env[:request][:boundary] ||= DEFAULT_MULTIPART_BOUNDARY
|
32
|
+
env[:request_headers][CONTENT_TYPE] ||= MULTIPART_MIME_TYPE
|
33
|
+
env[:request_headers][CONTENT_TYPE] += ";boundary=#{env[:request][:boundary]}"
|
34
|
+
env[:body] = create_multipart(env, env[:body])
|
35
|
+
else
|
36
|
+
env[:request_headers][CONTENT_TYPE] ||= URL_ENCODED_MIME_TYPE
|
37
|
+
env[:body] = Faraday::Utils::ParamsHash[env[:body]].to_query
|
38
|
+
end
|
37
39
|
end
|
38
40
|
end
|
41
|
+
@app.call env
|
39
42
|
end
|
40
|
-
@app.call env
|
41
|
-
end
|
42
43
|
|
43
44
|
|
44
|
-
|
45
|
+
private
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
def has_multipart?(obj)
|
48
|
+
# string is an enum in 1.8, returning list of itself
|
49
|
+
if obj.respond_to?(:each) && !obj.is_a?(String)
|
50
|
+
(obj.respond_to?(:values) ? obj.values : obj).each do |val|
|
51
|
+
return true if (val.respond_to?(:content_type) || has_multipart?(val))
|
52
|
+
end
|
51
53
|
end
|
54
|
+
false
|
52
55
|
end
|
53
|
-
false
|
54
|
-
end
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
def create_multipart(env, params)
|
58
|
+
boundary = env[:request][:boundary]
|
59
|
+
parts = process_params(params) do |key, value|
|
60
|
+
Faraday::Parts::Part.new(boundary, key, value)
|
61
|
+
end
|
62
|
+
parts << Faraday::Parts::EpiloguePart.new(boundary)
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
body = Faraday::CompositeReadIO.new(parts)
|
65
|
+
env[:request_headers][Faraday::Env::ContentLength] = body.length.to_s
|
66
|
+
body
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
69
|
+
def process_params(params, prefix = nil, pieces = nil, &block)
|
70
|
+
params.inject(pieces || []) do |all, (key, value)|
|
71
|
+
key = "#{prefix}[#{key}]" if prefix
|
72
|
+
|
73
|
+
case value
|
74
|
+
when Array
|
75
|
+
values = value.inject([]) { |a,v| a << [nil, v] }
|
76
|
+
process_params(values, key, all, &block)
|
77
|
+
when Hash
|
78
|
+
process_params(value, key, all, &block)
|
79
|
+
else
|
80
|
+
all << block.call(key, value)
|
81
|
+
end
|
80
82
|
end
|
81
83
|
end
|
82
84
|
end
|
83
|
-
end
|
84
85
|
|
86
|
+
end
|
85
87
|
end
|
86
88
|
end
|
@@ -1,24 +1,27 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
|
3
3
|
|
4
|
-
module Saddle::Middleware
|
5
|
-
module Response
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
5
|
+
module Saddle
|
6
|
+
module Middleware
|
7
|
+
module Response
|
8
|
+
|
9
|
+
# Public: Returns a default response in the case of an exception
|
10
|
+
# Expects default_response to be defined in the request of connection options, otherwise rethrows exception
|
11
|
+
class DefaultResponse < Faraday::Middleware
|
12
|
+
def call(env)
|
13
|
+
begin
|
14
|
+
@app.call(env)
|
15
|
+
rescue Faraday::Error
|
16
|
+
if res = env[:request][:default_response]
|
17
|
+
return ::Faraday::Response.new(:body => res)
|
18
|
+
else
|
19
|
+
raise
|
20
|
+
end
|
18
21
|
end
|
19
22
|
end
|
20
23
|
end
|
21
|
-
end
|
22
24
|
|
25
|
+
end
|
23
26
|
end
|
24
27
|
end
|
@@ -2,45 +2,47 @@ require 'faraday'
|
|
2
2
|
|
3
3
|
|
4
4
|
|
5
|
-
module Saddle
|
6
|
-
module
|
5
|
+
module Saddle
|
6
|
+
module Middleware
|
7
|
+
module Response
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
# Public: Parse response bodies as JSON.
|
10
|
+
class ParseJson < Faraday::Middleware
|
11
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
12
|
+
MIME_TYPE = 'application/json'.freeze
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
dependency do
|
15
|
+
require 'json' unless defined?(::JSON)
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
def call(env)
|
19
|
+
result = @app.call env
|
20
|
+
if parse_response?(env)
|
21
|
+
# Make sure we're working with a valid body that's not a String
|
22
|
+
if env[:body] && env[:body].respond_to?(:to_str)
|
23
|
+
env[:request_headers][CONTENT_TYPE] ||= MIME_TYPE
|
24
|
+
env[:body] = ::JSON.parse env[:body]
|
25
|
+
end
|
24
26
|
end
|
27
|
+
result
|
25
28
|
end
|
26
|
-
result
|
27
|
-
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
def parse_response?(env)
|
31
|
+
has_body?(env) and (response_type(env) == MIME_TYPE)
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
def has_body?(env)
|
35
|
+
body = env[:body] and !(body.respond_to?(:to_str) and body.empty?)
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
def response_type(env)
|
39
|
+
return nil unless env[:response_headers]
|
40
|
+
type = env[:response_headers][CONTENT_TYPE].to_s
|
41
|
+
type = type.split(';', 2).first if type.index(';')
|
42
|
+
type
|
43
|
+
end
|
42
44
|
end
|
43
|
-
end
|
44
45
|
|
46
|
+
end
|
45
47
|
end
|
46
48
|
end
|
@@ -1,24 +1,29 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
|
3
|
+
require 'saddle/errors'
|
3
4
|
|
4
|
-
module Saddle::Middleware
|
5
5
|
|
6
|
-
# Public: Enforces a ruby timeout on the request and throws one consistent
|
7
|
-
# exception for all classes of timeout, internal or from faraday.
|
8
|
-
# :timeout must be present in the request or client options
|
9
|
-
class RubyTimeout < Faraday::Middleware
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
module Saddle
|
8
|
+
module Middleware
|
9
|
+
|
10
|
+
# Public: Enforces a ruby timeout on the request and throws one consistent
|
11
|
+
# exception for all classes of timeout, internal or from faraday.
|
12
|
+
# :timeout must be present in the request or client options
|
13
|
+
class RubyTimeout < Faraday::Middleware
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
timeout = env[:request][:timeout] # nil or 0 means no timeout
|
17
|
+
Timeout.timeout(timeout, Saddle::TimeoutError) do
|
18
|
+
@app.call(env)
|
19
|
+
end
|
20
|
+
# It is possible that faraday will catch the timeout first and throw
|
21
|
+
# this exception, rethrow as a class derived from standard error.
|
22
|
+
rescue Faraday::Error::TimeoutError
|
23
|
+
raise Saddle::TimeoutError
|
15
24
|
end
|
16
|
-
|
17
|
-
# this exception, rethrow as a class derived from standard error.
|
18
|
-
rescue Faraday::Error::TimeoutError
|
19
|
-
raise Saddle::TimeoutError
|
25
|
+
|
20
26
|
end
|
21
27
|
|
22
28
|
end
|
23
|
-
|
24
29
|
end
|
data/lib/saddle/options.rb
CHANGED
@@ -1,73 +1,71 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
1
|
# Default options for a client. Override whatever you need to for
|
5
2
|
# your specific implementation
|
6
3
|
|
4
|
+
module Saddle
|
5
|
+
module Options
|
7
6
|
|
8
|
-
|
7
|
+
# Construct our default options, based upon the class methods
|
8
|
+
def default_options
|
9
|
+
{
|
10
|
+
:host => host,
|
11
|
+
:port => port,
|
12
|
+
:use_ssl => use_ssl,
|
13
|
+
:request_style => request_style,
|
14
|
+
:num_retries => num_retries,
|
15
|
+
:timeout => timeout,
|
16
|
+
:additional_middlewares => self.additional_middlewares,
|
17
|
+
:stubs => stubs,
|
18
|
+
}
|
19
|
+
end
|
9
20
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
:port => port,
|
15
|
-
:use_ssl => use_ssl,
|
16
|
-
:request_style => request_style,
|
17
|
-
:num_retries => num_retries,
|
18
|
-
:timeout => timeout,
|
19
|
-
:additional_middlewares => self.additional_middlewares,
|
20
|
-
:stubs => stubs,
|
21
|
-
}
|
22
|
-
end
|
21
|
+
# The default host for this client
|
22
|
+
def host
|
23
|
+
'localhost'
|
24
|
+
end
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
# The default port for this client
|
27
|
+
def port
|
28
|
+
80
|
29
|
+
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
# Should this client use SSL by default?
|
32
|
+
def use_ssl
|
33
|
+
false
|
34
|
+
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
# The POST/PUT style for this client
|
37
|
+
# options are [:json, :urlencoded]
|
38
|
+
def request_style
|
39
|
+
:json
|
40
|
+
end
|
38
41
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
42
|
+
# Default number of retries per request
|
43
|
+
def num_retries
|
44
|
+
3
|
45
|
+
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
# Default timeout per request (in seconds)
|
48
|
+
def timeout
|
49
|
+
30
|
50
|
+
end
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
# Use this to add additional middleware to the request stack
|
53
|
+
# ex:
|
54
|
+
# add_middleware({
|
55
|
+
# :klass => MyMiddleware,
|
56
|
+
# :args => [arg1, arg2],
|
57
|
+
# })
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
###
|
61
|
+
def add_middleware m
|
62
|
+
self.additional_middlewares << m
|
63
|
+
end
|
54
64
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# :args => [arg1, arg2],
|
60
|
-
# })
|
61
|
-
# end
|
62
|
-
#
|
63
|
-
###
|
64
|
-
def add_middleware m
|
65
|
-
self.additional_middlewares << m
|
66
|
-
end
|
65
|
+
# If the Typhoeus adapter is being used, pass stubs to it for testing.
|
66
|
+
def stubs
|
67
|
+
nil
|
68
|
+
end
|
67
69
|
|
68
|
-
# If the Typhoeus adapter is being used, pass stubs to it for testing.
|
69
|
-
def stubs
|
70
|
-
nil
|
71
70
|
end
|
72
|
-
|
73
71
|
end
|
data/lib/saddle/requester.rb
CHANGED
data/lib/saddle/version.rb
CHANGED
data/lib/saddle.rb
CHANGED
@@ -15,6 +15,10 @@ module Saddle
|
|
15
15
|
extend MethodTreeBuilder
|
16
16
|
extend Options
|
17
17
|
|
18
|
+
class << self
|
19
|
+
attr_accessor :additional_middlewares
|
20
|
+
end
|
21
|
+
|
18
22
|
# Once your implementation is written, this is the magic you need to
|
19
23
|
# create a client instance.
|
20
24
|
def self.create(opt={})
|
@@ -26,6 +30,13 @@ module Saddle
|
|
26
30
|
end
|
27
31
|
|
28
32
|
def self.inherited(obj)
|
33
|
+
# Clone the parent's additional_middlewares
|
34
|
+
obj.additional_middlewares = if defined?(obj.superclass.additional_middlewares)
|
35
|
+
(obj.superclass.additional_middlewares || []).clone
|
36
|
+
else
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
# Add additional client attributes
|
29
40
|
obj.send(:include, Saddle::ClientAttributes)
|
30
41
|
end
|
31
42
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'saddle'
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
### Make sure that multiple implementations of Saddle clients don't conflict with each other's middlewar.
|
7
|
+
|
8
|
+
describe Saddle::Client do
|
9
|
+
|
10
|
+
context "multiple implementations" do
|
11
|
+
context "using different middleware" do
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
# Middlewares
|
15
|
+
class Middleware1 < Faraday::Middleware
|
16
|
+
# Hook for catching calls
|
17
|
+
def subcall
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
self.subcall
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Middleware2 < Faraday::Middleware
|
27
|
+
# Hook for catching calls
|
28
|
+
def subcall
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env)
|
32
|
+
self.subcall
|
33
|
+
@app.call(env)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Clients
|
39
|
+
class Client1 < Saddle::Client
|
40
|
+
add_middleware({:klass => Middleware1})
|
41
|
+
end
|
42
|
+
class Client2 < Saddle::Client
|
43
|
+
add_middleware({:klass => Middleware2})
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not overlap" do
|
48
|
+
# Set up our stubs
|
49
|
+
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
50
|
+
stub.get('/') {
|
51
|
+
[
|
52
|
+
200,
|
53
|
+
{},
|
54
|
+
'Party on!',
|
55
|
+
]
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set up our clients
|
60
|
+
client1 = Client1.create(:stubs => stubs)
|
61
|
+
client2 = Client2.create(:stubs => stubs)
|
62
|
+
|
63
|
+
# Make sure client2's middleware isn't called
|
64
|
+
Middleware1.any_instance.should_receive(:subcall)
|
65
|
+
Middleware2.any_instance.should_not_receive(:subcall)
|
66
|
+
|
67
|
+
# Make the call
|
68
|
+
client1.requester.get('/')
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saddle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Lewis
|
@@ -82,6 +82,7 @@ files:
|
|
82
82
|
- lib/saddle/version.rb
|
83
83
|
- saddle.gemspec
|
84
84
|
- spec/requester/get_spec.rb
|
85
|
+
- spec/requester/multiple_spec.rb
|
85
86
|
- spec/requester/post_spec.rb
|
86
87
|
- spec/requester/retry_spec.rb
|
87
88
|
homepage: https://github.com/mLewisLogic/saddle
|
@@ -111,5 +112,6 @@ summary: A full-featured, generic consumer layer for you to build API client imp
|
|
111
112
|
with.
|
112
113
|
test_files:
|
113
114
|
- spec/requester/get_spec.rb
|
115
|
+
- spec/requester/multiple_spec.rb
|
114
116
|
- spec/requester/post_spec.rb
|
115
117
|
- spec/requester/retry_spec.rb
|