saddle 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZGNhYTRlODExY2JjZDgyN2NiZTQzZjQ3MThmODJiMjRjOGFmOWMwZQ==
4
+ ZTEzYTVkN2VkMjExYWQ4ZDg1OWY1NDFjMTI1M2E1NzliNTYwYTE3ZA==
5
5
  data.tar.gz: !binary |-
6
- MDE2M2U2ZWNkNzJlOWU4OTkwYTkxMTFmMjI4MmRlZTg0Yzk3YTRmMQ==
6
+ MzUyNzE1NDk1MDcxMmEzNzc2NzYyYjI0NjI5MGU0YmI0OWEwYzc4OQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- ZTI4ZTYzZGZhZGViMTBlYzMzNzM4ZjE5MDM2ODVhOTI1NjA2ZGUxYzRjZjA1
10
- ZWZiZWJiMDgyMzUyNTE3OWM3ZTMxNjBlMzcyNTI4NjJjNGRiYjNhYmIwYjU5
11
- NWYzZjE2OTYwMGQ0M2Q3Njk3ZjQ3MjUxNGNlNmQ5OGMwODk4ODc=
9
+ MDliMzRkYjYyY2VhMjlmNTgzNzM2ZTgxNjhiZGI2M2EzMzc5YmMwM2JmN2Ux
10
+ ZjgyOGU4NjZlZGNjN2Q4NzNiOGIyYzgzY2U2NmExY2E2OTRlZThjM2JmMDMy
11
+ MTgxMDE2ZjZmMmE3OTA2OTE4YTE3YmE5NDQ4ODVmZGVjMmFmNDY=
12
12
  data.tar.gz: !binary |-
13
- ODM2NzNmZTRkNTcxNDVlMGU1ZWI4NTVjMTMzMjdlNWQzYjI5ZWY5MTBhNDcy
14
- MDIyZWMwZGIyN2Q2Y2M2YTdhNGM1YmYwMDcxODVhODRhN2ViODQzZWY4MmZl
15
- MmJhMWNhMmQ5MDJhYjUwMDlhZjE2ZDU1ZmRmYWE3OTIyY2RlYTA=
13
+ ZDM5MWY5NDRiYzBiMmM1NjM1ZTQzNzc1YmE1N2VjZDI1MjM0ZmU0ZGE3MWU4
14
+ NDI2ZTNiODI2MmYxYjZjMjA0MGUzYzUwZjNhOGRmMGEzNmY3OTBkODVkYTFl
15
+ MDJiMDZiYzA5YmY5OTJiNjg2MGY0YmJlZTExYTNiYTBkMjNlNTU=
@@ -0,0 +1,13 @@
1
+ # Ignore tempfiles
2
+ *.sw[op]
3
+ .DS_Store
4
+
5
+ # RVM
6
+ .rvmrc
7
+
8
+ # Gems
9
+ *.gem
10
+
11
+ # IDEs
12
+ .idea
13
+ .project
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require rspec/instafail
3
+ --format RSpec::Instafail
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+
4
+ gem 'activesupport', '>= 3.0', :require => 'active_support'
5
+ gem 'faraday', '~> 0.8.6'
6
+ gem 'faraday_middleware', '~> 0.9.0'
7
+
8
+ group :test do
9
+ gem 'rspec', '~> 2.13.0'
10
+ gem 'rspec-instafail', '~> 0.2'
11
+ end
@@ -0,0 +1,33 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ activesupport (3.2.13)
5
+ i18n (= 0.6.1)
6
+ multi_json (~> 1.0)
7
+ diff-lcs (1.2.1)
8
+ faraday (0.8.6)
9
+ multipart-post (~> 1.1)
10
+ faraday_middleware (0.9.0)
11
+ faraday (>= 0.7.4, < 0.9)
12
+ i18n (0.6.1)
13
+ multi_json (1.7.2)
14
+ multipart-post (1.2.0)
15
+ rspec (2.13.0)
16
+ rspec-core (~> 2.13.0)
17
+ rspec-expectations (~> 2.13.0)
18
+ rspec-mocks (~> 2.13.0)
19
+ rspec-core (2.13.1)
20
+ rspec-expectations (2.13.0)
21
+ diff-lcs (>= 1.1.3, < 2.0)
22
+ rspec-instafail (0.2.4)
23
+ rspec-mocks (2.13.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ activesupport (>= 3.0)
30
+ faraday (~> 0.8.6)
31
+ faraday_middleware (~> 0.9.0)
32
+ rspec (~> 2.13.0)
33
+ rspec-instafail (~> 0.2)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013, Mike Lewis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,39 @@
1
+ # saddle
2
+
3
+ Hey nerd! Wrangle your SOA!
4
+
5
+
6
+ Saddle makes writing service clients as easy as giving high fives.©™®℗☃✓
7
+
8
+ It's a full-featured generic consumer layer for you to build API client implementations on top of.
9
+
10
+
11
+ ## about
12
+
13
+ Ok, I love high fives, but what does Saddle do for me?
14
+
15
+ I'm glad you asked fellow nerd! Do you like automatic retries? Automatic multi-part file posting? I know I sure do!
16
+
17
+ Do you like sending your POSTs url-encoded? That's okay, I still love you anyways and Saddle has your back. Does your rampant OCD refuse to let you post in anything less structured than JSON? High five nerd, Saddle bleeds OCD. Just set your :post_style flag and fuhgedaboutit!
18
+
19
+ For an example of a concrete implementation, see [saddle-example](https://github.com/mLewisLogic/saddle-example)
20
+
21
+
22
+ ## features
23
+ * set default connection settings for your implementation
24
+ * post urlencoded or JSON
25
+ * auto-parse JSON responses
26
+ * automatic retries and exception throwing
27
+
28
+
29
+ ## guide
30
+ 1. Inherit your endpoint from BaseEndpoint and call .get or .post within its action methods
31
+ 2. Place those endpoints in the *endpoints* directory at the root of your client. Nest them if you like.
32
+ 3. Inherit your client from Saddle
33
+ 4. Call client.endpoint.action()
34
+
35
+
36
+ ## todo
37
+ * xml posting
38
+ * xml parsing
39
+ * clean up custom middleware loading interface
@@ -0,0 +1,32 @@
1
+ require 'saddle/method_tree_builder'
2
+ require 'saddle/options'
3
+ require 'saddle/requester'
4
+
5
+
6
+
7
+ # Ghost ride the whip.
8
+ # Inherit your client implementation from Saddle::Client
9
+ # then call YourCrayClient.create to get a client instance.
10
+
11
+
12
+ module Saddle
13
+
14
+ class Client
15
+
16
+ extend MethodTreeBuilder
17
+ extend Options
18
+
19
+
20
+ # Once your implementation is written, this is the magic you need to
21
+ # create a client instance.
22
+ def self.create(opt={})
23
+ self.build_tree(
24
+ Saddle::Requester.new(
25
+ default_options.merge(opt)
26
+ )
27
+ )
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,108 @@
1
+
2
+
3
+
4
+
5
+ module Saddle
6
+
7
+ # This base endpoint is what all implementation endpoints should inherit
8
+ # from. It automatically provides tree construction and traversal
9
+ # functionality. It also abstracts away url construction and requests to
10
+ # the underlying requester instance.
11
+
12
+ class BaseEndpoint
13
+
14
+ attr_reader :requester, :relative_path, :parent
15
+
16
+ # Each endpoint needs to have a requester in order to ... make ... uh ... requests.
17
+ def initialize(requester, relative_path=nil, parent=nil)
18
+ @requester = requester
19
+ @relative_path = relative_path
20
+ @parent = parent.is_a?(BaseEndpoint) ? parent : nil
21
+ end
22
+
23
+
24
+ # Provide GET functionality for the implementer class
25
+ def get(action, params={}, options={})
26
+ request(:get, action, params, options)
27
+ end
28
+
29
+ # Provide POST functionality for the implementer class
30
+ def post(action, params={}, options={})
31
+ request(:post, action, params, options)
32
+ end
33
+
34
+ # Provide PUT functionality for the implementer class
35
+ def put(action, params={}, options={})
36
+ request(:put, action, params, options)
37
+ end
38
+
39
+ # Provide DELETE functionality for the implementer class
40
+ def delete(action, params={}, options={})
41
+ request(:delete, action, params, options)
42
+ end
43
+
44
+ def request(method, action, params={}, options={})
45
+ # Augment in interesting options
46
+ options[:saddle] = {
47
+ :call_chain => path_array,
48
+ :action => action,
49
+ }
50
+ @requester.send(method, path(action), params, options)
51
+ end
52
+
53
+
54
+ # Get the url path for this endpoint/action combo
55
+ def path(action=nil)
56
+ paths = path_array
57
+ paths << action unless action.nil?
58
+ '/' + paths.join('/')
59
+ end
60
+
61
+ def path_array
62
+ endpoint_chain.map(&:relative_path).reject{|p| p.nil?}
63
+ end
64
+
65
+ # Get the parent chain that led to this endpoint
66
+ def endpoint_chain
67
+ chain = []
68
+ node = self
69
+ until node.nil?
70
+ chain << node
71
+ node = node.parent
72
+ end
73
+ chain.reverse
74
+ end
75
+
76
+
77
+ # Create an endpoint instance and foist it upon this node
78
+ def build_and_attach_node(endpoint_class, method_name)
79
+ endpoint_instance = endpoint_class.new(@requester, method_name, self)
80
+ self.instance_variable_set("@#{method_name}", endpoint_instance)
81
+ self.class.class_eval { define_method(method_name) { endpoint_instance } }
82
+ endpoint_instance
83
+ end
84
+
85
+ # This will create a resource endpoint, based upon the parameters
86
+ # of this current node endpoint
87
+ def create_resource_endpoint(endpoint_class, resource_id)
88
+ endpoint_class.new(
89
+ @requester,
90
+ (path_array + [resource_id]).join('/')
91
+ # no parent so that it can free up memory
92
+ )
93
+ end
94
+ end
95
+
96
+
97
+
98
+
99
+ # This endpoint will be automatically constructed into the node
100
+ # traversal tree.
101
+ class TraversalEndpoint < BaseEndpoint; end
102
+
103
+
104
+ # This endpoint is used for constructing resource-style endpoints. This
105
+ # means it will NOT be automatically added into the traversal tree.
106
+ class ResourceEndpoint < BaseEndpoint; end
107
+
108
+ end
@@ -0,0 +1,113 @@
1
+ require 'active_support'
2
+
3
+ require 'saddle/endpoint'
4
+
5
+
6
+
7
+ # This mixin provides functionality for the construction of the endpoint
8
+ # tree. It will start at the root directory and namespace of the client
9
+ # implementation. It will then load the 'endpoints' directory and
10
+ # build the endpoint tree based upon module/class namespaces
11
+
12
+
13
+ module Saddle::MethodTreeBuilder
14
+
15
+ # Build out the endpoint structure from the root of the implementation
16
+ def build_tree(requester)
17
+ root_node = build_root_node(requester)
18
+ # If we have an 'endpoints' directory, build it out
19
+ if knows_root? && Dir.exists?(endpoints_directory)
20
+ Dir["#{endpoints_directory}/**/*.rb"].each { |f| load(f) }
21
+ build_node_children(self.endpoints_module, root_node, requester)
22
+ end
23
+ root_node
24
+ end
25
+
26
+
27
+ # Build our root node here. The root node is special in that it lives below
28
+ # the 'endpoints' directory, and so we need to manually check if it exists.
29
+ def build_root_node(requester)
30
+ if knows_root?
31
+ root_endpoint_file = File.join(
32
+ @@implementation_root,
33
+ 'root_endpoint.rb'
34
+ )
35
+ if File.file?(root_endpoint_file)
36
+ # Load it and create our base endpoint
37
+ load(root_endpoint_file)
38
+ self.implementation_module::RootEndpoint.new(requester)
39
+ else
40
+ # 'root_endpoint.rb' doesn't exist, so create a dummy endpoint
41
+ Saddle::BaseEndpoint.new(requester)
42
+ end
43
+ else
44
+ # we don't even have an implementation root, so create a dummy endpoint
45
+ Saddle::BaseEndpoint.new(requester)
46
+ end
47
+ end
48
+
49
+
50
+ # Build out the traversal tree by module namespace
51
+ def build_node_children(current_module, current_node, requester)
52
+ current_module.constants.each do |const_symbol|
53
+ const = current_module.const_get(const_symbol)
54
+
55
+ if const.class == Module
56
+ # A module means that it's a branch
57
+ # Build the branch out with a base endpoint
58
+ branch_node = current_node.build_and_attach_node(
59
+ Saddle::BaseEndpoint,
60
+ ActiveSupport::Inflector.underscore(const_symbol)
61
+ )
62
+ # Build out the branch's endpoints on the new branch node
63
+ self.build_node_children(const, branch_node, requester)
64
+ end
65
+
66
+ if const < Saddle::TraversalEndpoint
67
+ # A class means that it's a node
68
+ # Build out this endpoint on the current node
69
+ current_node.build_and_attach_node(
70
+ const,
71
+ ActiveSupport::Inflector.underscore(const_symbol)
72
+ )
73
+ end
74
+ end
75
+ end
76
+
77
+
78
+ # Get the module that the client implementation belongs to. This will act
79
+ # as the root namespace for endpoint traversal and construction
80
+ def implementation_module
81
+ ::ActiveSupport::Inflector.constantize(
82
+ self.name.split('::')[0..-2].join('::')
83
+ )
84
+ end
85
+
86
+ # Get the Endpoints module that lives within this implementation's
87
+ # namespace
88
+ def endpoints_module
89
+ implementation_module.const_get('Endpoints')
90
+ end
91
+
92
+ # Get the path to the 'endpoints' directory, based upon the client
93
+ # class that inherited Saddle
94
+ def endpoints_directory
95
+ File.join(@@implementation_root, 'endpoints')
96
+ end
97
+
98
+
99
+
100
+ # When Saddle is inherited, we store the root of the implementation class
101
+ # This is so that we know where to look for relative files, like
102
+ # the endpoints directory
103
+ def inherited(obj)
104
+ path, = caller[0].partition(":")
105
+ @@implementation_root = File.dirname(path)
106
+ end
107
+
108
+ # If this client was not fully constructed, it may not even have an
109
+ # implementation root. Allow that behavior and avoid firesystem searching.
110
+ def knows_root?
111
+ defined?(@@implementation_root)
112
+ end
113
+ end
@@ -0,0 +1,30 @@
1
+ require 'faraday'
2
+
3
+
4
+ module Saddle::Middleware
5
+
6
+ # Public: Reports exceptions to airbrake
7
+ #
8
+ class Airbrake < Faraday::Middleware
9
+
10
+ def initialize(app, airbrake_api_key)
11
+ super(app)
12
+ ::Airbrake.configure do |config|
13
+ config.api_key = airbrake_api_key
14
+ # TODO: filter sensitive info
15
+ # config.params_filters.concat(Security::SENSITIVE_PARAMS_STR)
16
+ end
17
+ end
18
+
19
+ def call(env)
20
+ begin
21
+ @app.call(env)
22
+ rescue => e
23
+ ::Airbrake.notify(e)
24
+ raise
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,24 @@
1
+ require 'faraday'
2
+
3
+
4
+ module Saddle::Middleware
5
+
6
+ # Public: Returns a default response in the case of an exception
7
+ # Expects default_response to be defined in the request of connection options, otherwise rethrows exception
8
+ class DefaultResponse < Faraday::Middleware
9
+
10
+ def call(env)
11
+ begin
12
+ @app.call(env)
13
+ rescue => e
14
+ if res = env[:request][:default_response]
15
+ return ::Faraday::Response.new(:body => res)
16
+ else
17
+ raise
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,36 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+
4
+ module Saddle::Middleware
5
+
6
+ # Public: Parse response bodies as JSON.
7
+ class ParseJson < FaradayMiddleware::ResponseMiddleware
8
+ MIME_TYPE = 'application/json'.freeze
9
+
10
+ dependency do
11
+ require 'json' unless defined?(::JSON)
12
+ end
13
+
14
+ define_parser do |body|
15
+ ::JSON.parse body unless body.strip.empty?
16
+ end
17
+
18
+
19
+ def parse_response?(env)
20
+ type = response_type(env)
21
+ super and has_body?(env) and (type.empty? or type == MIME_TYPE)
22
+ end
23
+
24
+ def has_body?(env)
25
+ body = env[:body] and !(body.respond_to?(:to_str) and body.empty?)
26
+ end
27
+
28
+ def response_type(env)
29
+ type = env[:response_headers][CONTENT_TYPE].to_s
30
+ type = type.split(';', 2).first if type.index(';')
31
+ type
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,19 @@
1
+ require 'faraday'
2
+
3
+
4
+ module Saddle::Middleware
5
+
6
+ # Public: Enforces a ruby timeout on the request
7
+ # :timeout must be present in the request or client options
8
+ class RubyTimeout < Faraday::Middleware
9
+
10
+ def call(env)
11
+ timeout = env[:request][:timeout] # nil or 0 means no timeout
12
+ Timeout.timeout(timeout) do
13
+ @app.call(env)
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,45 @@
1
+ require 'statsd'
2
+
3
+ module Saddle::Middleware
4
+
5
+ # Public: Wraps request with statsd logging
6
+ # Expects statsd_path in request options. However, if using saddle and no statsd_path is specified
7
+ # will read call_chain and action and use them to construct a statsd_path
8
+ class StatsdLogging < Faraday::Middleware
9
+ attr_accessor :graphite_host, :graphite_port, :namespace
10
+
11
+ def initialize(app, graphite_host, graphite_port=nil, namespace=nil)
12
+ super(app)
13
+ @graphite_host = graphite_host
14
+ @graphite_port = graphite_port
15
+ @namespace = namespace
16
+ self.statsd
17
+ end
18
+
19
+ def statsd
20
+ if(@statsd.nil?)
21
+ @statsd = Statsd.new(@graphite_host, @graphite_port)
22
+ @statsd.namespace = @namespace if @namespace
23
+ end
24
+ return @statsd
25
+ end
26
+
27
+ def call(env)
28
+ if env[:request][:statsd_path]
29
+ statsd_path = env[:request][:statsd_path]
30
+ elsif env[:request][:saddle] && env[:request][:saddle][:call_chain] && env[:request][:saddle][:action]
31
+ statsd_path = (env[:request][:saddle][:call_chain] + [env[:request][:saddle][:action]]).join(".")
32
+ end
33
+
34
+ if statsd_path
35
+ self.statsd.time statsd_path do
36
+ @app.call(env)
37
+ end
38
+ else
39
+ @app.call(env)
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,80 @@
1
+
2
+
3
+
4
+ # Default options for a client. Override whatever you need to for
5
+ # your specific implementation
6
+
7
+
8
+ module Saddle::Options
9
+
10
+ # Construct our default options, based upon the class methods
11
+ def default_options
12
+ {
13
+ :host => host,
14
+ :port => port,
15
+ :use_ssl => use_ssl,
16
+ :post_style => post_style,
17
+ :response_style => response_style,
18
+ :num_retries => num_retries,
19
+ :timeout => timeout,
20
+ :additional_middlewares => additional_middlewares,
21
+ :stubs => stubs,
22
+ }
23
+ end
24
+
25
+ # The default host for this client
26
+ def host
27
+ 'localhost'
28
+ end
29
+
30
+ # The default port for this client
31
+ def port
32
+ 80
33
+ end
34
+
35
+ # Should this client use SSL by default?
36
+ def use_ssl
37
+ false
38
+ end
39
+
40
+ # The POST/PUT style for this client
41
+ # options are [:json, :urlencoded]
42
+ def post_style
43
+ :json
44
+ end
45
+
46
+ # How to parse results
47
+ # options are [:json, :urlencoded]
48
+ def response_style
49
+ :json
50
+ end
51
+
52
+ # Default number of retries per request
53
+ def num_retries
54
+ 3
55
+ end
56
+
57
+ # Default timeout per request (in seconds)
58
+ def timeout
59
+ 30
60
+ end
61
+
62
+ # Override this to add additional middleware to the request stack
63
+ # ex:
64
+ #
65
+ # require 'my_middleware'
66
+ # def self.default_middleware
67
+ # [MyMiddleware]
68
+ # end
69
+ #
70
+ ###
71
+ def additional_middlewares
72
+ []
73
+ end
74
+
75
+ # If the Typhoeus adapter is being used, pass stubs to it for testing.
76
+ def stubs
77
+ nil
78
+ end
79
+
80
+ end
@@ -0,0 +1,159 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+
4
+ require 'saddle/middleware/default_response'
5
+ require 'saddle/middleware/parse_json'
6
+ require 'saddle/middleware/ruby_timeout'
7
+
8
+
9
+
10
+ # The requester handles setting up the network connecting and brokering
11
+ # requests.
12
+
13
+
14
+ module Saddle
15
+
16
+ class Requester
17
+
18
+ VALID_BODY_STYLES = [:json, :urlencoded]
19
+
20
+ # Available options
21
+ ## host - host to connect to (default: localhost)
22
+ ## port - port to connect on (default: 80)
23
+ ## use_ssl - true if we should use https (default: false)
24
+ ## post_style - :json or :urlencoded (default: :json)
25
+ ## response_style - :json or :urlencoded (default: :json)
26
+ ## num_retries - number of times to retry each request (default: 3)
27
+ ## timeout - timeout in seconds
28
+ ## additional_middleware - an Array of more middlewares to apply to the top of the stack
29
+ ## - each middleware consists of hash of klass, and optionally args (an array)
30
+ ## stubs - test stubs for specs
31
+ def initialize(opt={})
32
+ @host = opt[:host] || 'localhost'
33
+ raise ':host must be a string' unless @host.is_a?(String)
34
+ @port = opt[:port] || 80
35
+ raise ':port must be an integer' unless @port.is_a?(Fixnum)
36
+ @use_ssl = opt[:use_ssl] || false
37
+ raise ':use_ssl must be true or false' unless (@use_ssl.is_a?(TrueClass) || @use_ssl.is_a?(FalseClass))
38
+ @post_style = opt[:post_style] || :json
39
+ raise ":post_style must be in: #{VALID_BODY_STYLES.join(',')}" unless VALID_BODY_STYLES.include?(@post_style)
40
+ @response_style = opt[:response_style] || :json
41
+ raise ":response_style must be in: #{VALID_BODY_STYLES.join(',')}" unless VALID_BODY_STYLES.include?(@response_style)
42
+ @num_retries = opt[:num_retries] || 3
43
+ raise ':num_retries must be an integer' unless @num_retries.is_a?(Fixnum)
44
+ @timeout = opt[:timeout]
45
+ unless @timeout.nil?
46
+ raise ':timeout must be a number or nil' unless @timeout.is_a?(Numeric)
47
+ end
48
+ @additional_middlewares = opt[:additional_middlewares] || []
49
+ raise ':additional_middleware must be an Array' unless @additional_middlewares.is_a?(Array)
50
+ raise 'invalid middleware found' unless @additional_middlewares.all? { |m| m[:klass] < Faraday::Middleware }
51
+ raise 'middleware arguments must be an array' unless @additional_middlewares.all? { |m| m[:args].nil? || m[:args].is_a?(Array) }
52
+ @stubs = opt[:stubs] || nil
53
+ unless @stubs.nil?
54
+ raise ':stubs must be a Faraday::Adapter::Test::Stubs' unless @stubs.is_a?(Faraday::Adapter::Test::Stubs)
55
+ end
56
+ end
57
+
58
+
59
+ # Make a GET request
60
+ def get(url, params={}, options={})
61
+ response = connection.get do |req|
62
+ req.options.merge! options
63
+ req.url url, params
64
+ end
65
+ response.body
66
+ end
67
+
68
+ # Handle request logic for PUT or POST
69
+ def post_or_put(f, url, params={}, options={})
70
+ response = connection.send(f) do |req|
71
+ req.options.merge! options
72
+ req.url url
73
+ # Handle different supported post styles
74
+ case @post_style
75
+ when :json
76
+ req.headers['Content-Type'] = 'application/json'
77
+ req.body = params.to_json
78
+ when :urlencoded
79
+ req.params = params
80
+ else
81
+ raise RuntimeError(":post_style must be one of: #{VALID_POST_STYLES.join(',')}")
82
+ end
83
+ end
84
+ response.body
85
+ end
86
+
87
+ # Make a POST request
88
+ def post(url, params={}, options={})
89
+ post_or_put(:post, url, params, options)
90
+ end
91
+
92
+ # Make a PUT request
93
+ def put(url, params={}, options={})
94
+ post_or_put(:put, url, params, options)
95
+ end
96
+
97
+ # Make a DELETE request
98
+ def delete(url, params={}, options={})
99
+ response = connection.delete do |req|
100
+ req.options.merge! options
101
+ req.url url, params
102
+ end
103
+ response.body
104
+ end
105
+
106
+
107
+
108
+ private
109
+
110
+ # Construct a base url using this requester's settings
111
+ def base_url
112
+ "http#{'s' if @use_ssl}://#{@host}:#{@port}"
113
+ end
114
+
115
+ # Build a connection instance, wrapped in the middleware that we want
116
+ def connection
117
+ @connection ||= Faraday.new(base_url) do |builder|
118
+ # Config options
119
+ unless @timeout.nil?
120
+ builder.options[:timeout] = @timeout
121
+ end
122
+
123
+ # Support default return values upon exception
124
+ builder.use Saddle::Middleware::DefaultResponse
125
+
126
+ # Apply additional implementation-specific middlewares
127
+ @additional_middlewares.each do |m|
128
+ builder.use m[:klass], *m[:args]
129
+ end
130
+
131
+ # Hard timeout on the entire request
132
+ builder.use Saddle::Middleware::RubyTimeout
133
+
134
+ # Support multi-part encoding if there is a file attached
135
+ builder.request :multipart
136
+ # Handle retries
137
+ if @num_retries
138
+ builder.request :retry, @num_retries
139
+ end
140
+
141
+ # Set up our adapter
142
+ if @stubs.nil?
143
+ # Use the default adapter
144
+ builder.adapter :net_http
145
+ else
146
+ # Use the test adapter
147
+ builder.adapter :test, @stubs
148
+ end
149
+
150
+ # Raise exceptions on 4xx and 5xx errors
151
+ builder.response :raise_error
152
+ # Handle parsing out the response if it's JSON
153
+ builder.use Saddle::Middleware::ParseJson
154
+ end
155
+ end
156
+
157
+ end
158
+
159
+ end
@@ -0,0 +1,3 @@
1
+ module Saddle
2
+ VERSION = '0.0.4'
3
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/saddle/version', __FILE__)
3
+
4
+ lib = File.expand_path('../lib', __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'saddle'
9
+ s.version = Saddle::VERSION
10
+
11
+ s.authors = ['Mike Lewis', 'Naseem Hakim']
12
+ s.email = 'mike@cleverkoala.com'
13
+ s.description = %q{Makes writing API clients as easy as giving high fives}
14
+ s.summary = %q{
15
+ A generic client wrapper for building service-specific wrappers. Base functionality, meant to be extended to concrete implementations.
16
+ }
17
+ s.homepage = 'https://github.com/mLewisLogic/saddle'
18
+ s.license = 'MIT'
19
+
20
+ s.require_path = 'lib'
21
+ s.files = `git ls-files`.split($\)
22
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
24
+
25
+ s.add_dependency 'activesupport', '>= 3.0'
26
+ s.add_dependency 'faraday', '~> 0.8.6'
27
+ s.add_dependency 'faraday_middleware', '~> 0.9.0'
28
+ end
@@ -0,0 +1,34 @@
1
+ require 'saddle'
2
+
3
+ describe Saddle::Client do
4
+
5
+ context "instance" do
6
+ before :each do
7
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
8
+ stub.get('/test') {
9
+ [
10
+ 200,
11
+ {'Content-Type' => 'application/x-www-form-urlencoded'},
12
+ 'success'
13
+ ]
14
+ }
15
+ stub.get('/test.json') {
16
+ [
17
+ 200,
18
+ {'Content-Type' => 'application/json'},
19
+ {'success' => true}.to_json
20
+ ]
21
+ }
22
+ end
23
+ @client = Saddle::Client.create(:stubs => stubs)
24
+ end
25
+
26
+ it "should be able to request urlencoded" do
27
+ @client.requester.get('/test').should == 'success'
28
+ end
29
+
30
+ it "should be able to request JSON encoded" do
31
+ @client.requester.get('/test.json')['success'].should == true
32
+ end
33
+ end
34
+ 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.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Lewis
@@ -58,7 +58,26 @@ email: mike@cleverkoala.com
58
58
  executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
- files: []
61
+ files:
62
+ - .gitignore
63
+ - .rspec
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - LICENSE
67
+ - README.md
68
+ - lib/saddle.rb
69
+ - lib/saddle/endpoint.rb
70
+ - lib/saddle/method_tree_builder.rb
71
+ - lib/saddle/middleware/airbrake.rb
72
+ - lib/saddle/middleware/default_response.rb
73
+ - lib/saddle/middleware/parse_json.rb
74
+ - lib/saddle/middleware/ruby_timeout.rb
75
+ - lib/saddle/middleware/statsd_logging.rb
76
+ - lib/saddle/options.rb
77
+ - lib/saddle/requester.rb
78
+ - lib/saddle/version.rb
79
+ - saddle.gemspec
80
+ - spec/saddle_client_spec.rb
62
81
  homepage: https://github.com/mLewisLogic/saddle
63
82
  licenses:
64
83
  - MIT
@@ -84,4 +103,5 @@ signing_key:
84
103
  specification_version: 4
85
104
  summary: A generic client wrapper for building service-specific wrappers. Base functionality,
86
105
  meant to be extended to concrete implementations.
87
- test_files: []
106
+ test_files:
107
+ - spec/saddle_client_spec.rb