weary 0.7.2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +4 -1
  2. data/.rspec +2 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +11 -8
  5. data/Gemfile.lock +49 -53
  6. data/LICENSE +1 -1
  7. data/README.md +134 -208
  8. data/Rakefile +6 -47
  9. data/lib/weary.rb +4 -66
  10. data/lib/weary/adapter.rb +23 -0
  11. data/lib/weary/adapters/net_http.rb +68 -0
  12. data/lib/weary/client.rb +243 -0
  13. data/lib/weary/deferred.rb +35 -0
  14. data/lib/weary/env.rb +32 -0
  15. data/lib/weary/middleware.rb +9 -0
  16. data/lib/weary/middleware/basic_auth.rb +17 -0
  17. data/lib/weary/middleware/content_type.rb +28 -0
  18. data/lib/weary/middleware/oauth.rb +31 -0
  19. data/lib/weary/request.rb +137 -124
  20. data/lib/weary/resource.rb +152 -128
  21. data/lib/weary/response.rb +48 -99
  22. data/lib/weary/route.rb +53 -0
  23. data/lib/weary/version.rb +3 -0
  24. data/spec/spec_helper.rb +4 -56
  25. data/spec/support/shared_examples_for_a_rack_app.rb +70 -0
  26. data/spec/support/shared_examples_for_a_rack_env.rb +14 -0
  27. data/spec/support/shared_examples_for_a_uri.rb +9 -0
  28. data/spec/weary/adapter_spec.rb +26 -0
  29. data/spec/weary/adapters/nethttp_spec.rb +88 -0
  30. data/spec/weary/client_spec.rb +258 -0
  31. data/spec/weary/deferred_spec.rb +35 -0
  32. data/spec/weary/env_spec.rb +12 -0
  33. data/spec/weary/middleware/basic_auth_spec.rb +23 -0
  34. data/spec/weary/middleware/content_type_spec.rb +34 -0
  35. data/spec/weary/middleware/oauth_spec.rb +27 -0
  36. data/spec/weary/request_spec.rb +227 -315
  37. data/spec/weary/resource_spec.rb +233 -233
  38. data/spec/weary/response_spec.rb +82 -159
  39. data/spec/weary/route_spec.rb +72 -0
  40. data/spec/weary_spec.rb +3 -56
  41. data/weary.gemspec +16 -79
  42. metadata +138 -98
  43. data/VERSION +0 -1
  44. data/examples/batch.rb +0 -20
  45. data/examples/repo.rb +0 -16
  46. data/examples/status.rb +0 -26
  47. data/lib/weary/base.rb +0 -124
  48. data/lib/weary/batch.rb +0 -37
  49. data/lib/weary/exceptions.rb +0 -15
  50. data/lib/weary/httpverb.rb +0 -32
  51. data/spec/fixtures/github.yml +0 -11
  52. data/spec/fixtures/twitter.xml +0 -763
  53. data/spec/fixtures/vimeo.json +0 -1
  54. data/spec/weary/base_spec.rb +0 -320
  55. data/spec/weary/batch_spec.rb +0 -71
  56. data/spec/weary/httpverb_spec.rb +0 -25
data/Rakefile CHANGED
@@ -1,51 +1,10 @@
1
- begin
2
- # Try to require the preresolved locked set of gems.
3
- require File.expand_path('../.bundle/environment', __FILE__)
4
- rescue LoadError
5
- # Fall back on doing an unlocked resolve at runtime.
6
- require "rubygems"
7
- require "bundler"
8
- Bundler.setup
9
- end
10
-
11
- require 'spec/rake/spectask'
1
+ require 'rspec/core/rake_task'
2
+ require "bundler/gem_tasks"
3
+ require 'yard'
12
4
 
13
5
  task :default => :spec
14
6
 
15
- require 'rake/rdoctask'
16
- Rake::RDocTask.new do |rdoc|
17
- rdoc.rdoc_dir = 'doc'
18
- rdoc.title = 'weary'
19
- rdoc.main = 'README.md'
20
- rdoc.rdoc_files.include('README.*', 'lib/**/*.rb', 'LICENSE')
21
- rdoc.options << '--inline-source'
22
- end
23
-
24
- begin
25
- require 'jeweler'
26
- Jeweler::Tasks.new do |gemspec|
27
- gemspec.name = "weary"
28
- gemspec.rubyforge_project = "weary"
29
- gemspec.summary = "A little DSL for consuming RESTful web services"
30
- gemspec.email = "mark@markwunsch.com"
31
- gemspec.homepage = "http://github.com/mwunsch/weary"
32
- gemspec.description = "A tiny DSL that makes the consumption of RESTful web services simple."
33
- gemspec.authors = "Mark Wunsch"
34
- gemspec.add_dependency 'crack', '>= 0.1.7'
35
- gemspec.add_dependency 'oauth', '>= 0.3.5'
36
- gemspec.add_development_dependency 'bundler', ">= 0.9.7"
37
- end
38
- Jeweler::GemcutterTasks.new
39
- rescue LoadError
40
- puts "Jeweler not available. Install it with: gem install jeweler"
41
- end
42
-
43
- desc "Open an irb session preloaded with this library"
44
- task :console do
45
- sh "irb -rubygems -I lib -r weary.rb"
46
- end
7
+ RSpec::Core::RakeTask.new(:spec)
47
8
 
48
- Spec::Rake::SpecTask.new do |t|
49
- t.spec_files = FileList['spec/**/*_spec.rb']
50
- t.spec_opts = ['--color','--format nested']
51
- end
9
+ # The state of yard-tomdoc isn't so great right now
10
+ YARD::Rake::YardocTask.new
data/lib/weary.rb CHANGED
@@ -1,34 +1,8 @@
1
- require 'uri'
2
- require 'net/http'
3
- require 'net/https'
4
-
5
- require 'crack'
6
- require 'oauth'
7
-
8
- autoload :Yaml, 'yaml'
9
-
10
- require 'weary/request'
11
- require 'weary/response'
12
- require 'weary/resource'
13
- require 'weary/batch'
14
- require 'weary/exceptions'
15
- require 'weary/httpverb'
16
- require 'weary/base'
1
+ require 'weary/version'
2
+ require 'weary/client'
17
3
 
18
4
  module Weary
19
-
20
- # Supported HTTP Verbs
21
- Methods = [:get, :post, :put, :delete, :head]
22
-
23
- # Supported Content Types
24
- ContentTypes = { :json => [:json, 'json', 'application/json', 'text/json', 'application/javascript', 'text/javascript'],
25
- :xml => [:xml, 'xml', 'text/xml', 'application/xml'],
26
- :html => [:html, 'html', 'text/html'],
27
- :yaml => [:yaml, 'yaml', 'application/x-yaml', 'text/yaml'],
28
- :plain => [:plain, 'plain', 'text/plain'] }
29
-
30
- # A collection of User Agent strings that I stole from HURL (http://hurl.it)
31
- UserAgents = {
5
+ USER_AGENTS = {
32
6
  "Firefox 1.5.0.12 - Mac" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.12) Gecko/20070508 Firefox/1.5.0.12",
33
7
  "Firefox 1.5.0.12 - Windows" => "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.12) Gecko/20070508 Firefox/1.5.0.12",
34
8
  "Firefox 2.0.0.12 - Mac" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12",
@@ -58,40 +32,4 @@ module Weary
58
32
  "Safari 4.0.2 - Mac" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-us) AppleWebKit/530.19.2 (KHTML, like Gecko) Version/4.0.2 Safari/530.19",
59
33
  "Safari 4.0.2 - Windows" => "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/530.19.2 (KHTML, like Gecko) Version/4.0.2 Safari/530.19.1"
60
34
  }
61
-
62
- class << self
63
- def get(url,&block)
64
- request url, :get, &block
65
- end
66
-
67
- def post(url,&block)
68
- request url, :post, &block
69
- end
70
-
71
- def put(url,&block)
72
- request url, :put, &block
73
- end
74
-
75
- def delete(url,&block)
76
- request url, :delete, &block
77
- end
78
-
79
- def head(url,&block)
80
- request url, :head, &block
81
- end
82
-
83
- # Create a Request for the URL.
84
- # Defaults to a GET Request. Use a block to further modify the Request
85
- def request(url,via = :get, &block)
86
- req = Request.new(url,via)
87
- yield req if block_given?
88
- req
89
- end
90
-
91
- # Create a Batch for a group of Requests
92
- def batch(*requests)
93
- Batch.new(requests)
94
- end
95
- end
96
-
97
- end
35
+ end
@@ -0,0 +1,23 @@
1
+ require 'weary/response'
2
+ require 'weary/adapters/net_http'
3
+ module Weary
4
+ # An abstract interface. A subclass should be something that actually opens
5
+ # a socket to make the request, e.g. Net/HTTP, Curb, etc.
6
+ module Adapter
7
+
8
+ def initialize(app=nil)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ connect(Rack::Request.new(env)).finish
14
+ end
15
+
16
+ # request is a Rack::Request
17
+ # This computation is performed in a Promise/Future
18
+ # Returns a Rack::Response
19
+ def connect(request)
20
+ Rack::Response.new [""], 501, {"Content-Type" => "text/plain"}
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+
4
+ module Weary
5
+ module Adapter
6
+ class NetHttp
7
+ include Weary::Adapter
8
+
9
+ def self.call(env)
10
+ connect(Rack::Request.new(env)).finish
11
+ end
12
+
13
+ def self.connect(request)
14
+ connection = socket(request)
15
+ response = connection.request prepare(request)
16
+ Rack::Response.new response.body, response.code, normalize_response(response.to_hash)
17
+ end
18
+
19
+ def self.prepare(request)
20
+ req_class = request_class(request.request_method)
21
+ req = req_class.new(request.fullpath, normalize_request_headers(request.env))
22
+ if req.request_body_permitted? # What's the best way of passing the body?
23
+ req.body = request.body.read
24
+ request.body.rewind
25
+ end
26
+ req.content_type = request.content_type if request.content_type
27
+ req.content_length = request.content_length if request.content_length
28
+ req
29
+ end
30
+
31
+ def self.normalize_request_headers(env)
32
+ req_headers = env.reject {|k,v| !k.start_with? "HTTP_" }
33
+ normalized = req_headers.map do |k, v|
34
+ new_key = k.sub("HTTP_",'').split('_').map(&:capitalize).join('-')
35
+ [new_key, v] unless UNWANTED_REQUEST_HEADERS.include? new_key
36
+ end
37
+ Hash[normalized]
38
+ end
39
+
40
+ def self.normalize_response(headers)
41
+ headers.reject {|k,v| k.downcase == 'status' }
42
+ end
43
+
44
+ def self.socket(request)
45
+ host = request.env['HTTP_HOST'] || request.env['SERVER_NAME']
46
+ port = request.env['SERVER_PORT'].to_s
47
+ connection = Net::HTTP.new host, port
48
+ connection.use_ssl = request.scheme == 'https'
49
+ connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if connection.use_ssl?
50
+ connection
51
+ end
52
+
53
+ def self.request_class(method)
54
+ capitalized = method.capitalize
55
+ Net::HTTP.const_get capitalized
56
+ end
57
+
58
+ def call(env)
59
+ self.class.call(env)
60
+ end
61
+
62
+ private
63
+
64
+ UNWANTED_REQUEST_HEADERS = []
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,243 @@
1
+ require 'weary/resource'
2
+
3
+ module Weary
4
+ autoload :Route, 'weary/route'
5
+
6
+ # An abstract class used to construct client libraries and the primary
7
+ # entrance point to use the Weary framework. Client defines a DSL to describe
8
+ # and construct a set of Resources, which in turn can generate Requests that
9
+ # can perform HTTP actions.
10
+ #
11
+ # Resources are defined and stored by one of the class methods corresponding
12
+ # to an HTTP request method. Every Resource is declared with a name that
13
+ # acts as both a key to access the resource, and, when the class is
14
+ # instantiated, the name of a dynamically generated instance method.
15
+ #
16
+ # Examples
17
+ #
18
+ # class GiltSales < Weary::Client
19
+ # domain "https://api.gilt.com/v1"
20
+ #
21
+ # required :apikey
22
+ #
23
+ # optional :affid
24
+ #
25
+ # get :active, "/sales/active.json"
26
+ #
27
+ # get :active_in_store, "/sales/:store/active.json"
28
+ #
29
+ # get :upcoming, "/sales/upcoming.json"
30
+ #
31
+ # get :upcoming_in_store, "/sales/:store/upcoming.json"
32
+ #
33
+ # get :detail, "/sales/:store/:sale_key/detail.json"
34
+ # end
35
+ #
36
+ # sales = GiltSales.new
37
+ # sales.active_in_store :store => :women, :apikey => "my-key"
38
+ # # => A Weary::Request object
39
+ #
40
+ class Client
41
+
42
+ # Internal: HTTP Request verbs supported by Weary. These translate to class
43
+ # methods of Client.
44
+ REQUEST_METHODS = [
45
+ :copy, :delete, :get, :head, :lock, :mkcol, :move, :options,
46
+ :patch, :post, :propfind, :proppatch, :put, :trace, :unlock
47
+ ]
48
+
49
+ class << self
50
+
51
+ REQUEST_METHODS.each do |request_method|
52
+ # Generate a resource of the specified REQUEST_METHOD. This
53
+ # method is defined for each of the REQUEST_METHODS.
54
+ #
55
+ # name - A Symbol name or descriptor of a resource to dynamically
56
+ # generate an instance method of the same name.
57
+ # path - A String path to the resource. If the class's domain is set it
58
+ # will be prepended to this path to form the resource's uri.
59
+ # block - An optional block to be used to further customize the resource.
60
+ #
61
+ # Returns a Response object describing an HTTP endpoint.
62
+ #
63
+ # Signature
64
+ #
65
+ # <method>(name, path, &block)
66
+ #
67
+ # method - An HTTP request method (and member of REQUEST_METHODS).
68
+ define_method request_method do |name, path, &block|
69
+ resource(name, request_method.to_s.upcase, path, &block)
70
+ end
71
+ end
72
+
73
+ # An accessor to set the domain where the client's resources are
74
+ # located. This is prepended to the resources' path to form the resource
75
+ # uri.
76
+ #
77
+ # host - An optional String to set the client domain.
78
+ #
79
+ # Returns the domain String.
80
+ def domain(host=nil)
81
+ @domain = host unless host.nil?
82
+ @domain ||= ""
83
+ end
84
+
85
+ # An accessor to set optional parameters permitted by all
86
+ # resources described by the client.
87
+ #
88
+ # params - Zero or more Symbol parameters expected by the resources.
89
+ #
90
+ # Returns an Array of parameters.
91
+ def optional(*params)
92
+ @optional = params unless params.empty?
93
+ @optional ||= []
94
+ end
95
+
96
+ # An accessor to set parameters required by the all of the
97
+ # resources of the client.
98
+ #
99
+ # params - Zero or more Symbol parameters required by the resources.
100
+ #
101
+ # Returns an Array of parameters.
102
+ def required(*params)
103
+ @required = params unless params.empty?
104
+ @required ||= []
105
+ end
106
+
107
+ # An accessor to set default parameters to be used by the all of
108
+ # the resources described by the client.
109
+ #
110
+ # hash - An optional Hash of key/value pairs describing the
111
+ # default parameters to be sent to the resources
112
+ #
113
+ # Returns a Hash of the default parameters sent to all resources.
114
+ def defaults(hash=nil)
115
+ @defaults = hash unless hash.nil?
116
+ @defaults ||= {}
117
+ end
118
+
119
+ # An accessor to set HTTP request headers for all of the client's
120
+ # resources.
121
+ #
122
+ # hash - An optional Hash of key/value pairs that are sent as HTTP
123
+ # request headers when a resource's request is performed.
124
+ #
125
+ # Returns a Hash of the headers.
126
+ def headers(hash=nil)
127
+ @headers = hash unless hash.nil?
128
+ @headers ||= {}
129
+ end
130
+
131
+ # Send a Rack middleware to all of the requests generated by
132
+ # the client.
133
+ #
134
+ # middleware - An object that implements the rack middleware interface.
135
+ # args - Zero or more optional arguments to send to the middleware.
136
+ # block - An optional block to send to the middleware.
137
+ #
138
+ # Returns an Array of middlewares.
139
+ def use(middleware, *args, &block)
140
+ @middlewares ||= []
141
+ @middlewares << [middleware, args, block]
142
+ end
143
+
144
+ # Internal: Create and build a resource description of a request. The
145
+ # resource is then stored in an internal hash, generating an instance
146
+ # method.
147
+ #
148
+ # name - A Symbol name/descriptor of the resource.
149
+ # method - A Symbol or String of the request method used in the request.
150
+ # path - A String path to the resource that is appended to the domain
151
+ # to form the uri.
152
+ #
153
+ # Yields the Resource for further construction.
154
+ #
155
+ # Returns the generated Resource.
156
+ def resource(name, method, path="")
157
+ resource = Weary::Resource.new method, "#{domain}#{path}"
158
+ resource.optional *optional
159
+ resource.required *required
160
+ resource.defaults defaults
161
+ resource.headers headers
162
+ if !@middlewares.nil? && !@middlewares.empty?
163
+ @middlewares.each {|middleware| resource.use *middleware }
164
+ end
165
+ yield resource if block_given?
166
+ self[name] = resource
167
+ end
168
+
169
+ # A getter for the stored table of Resources.
170
+ #
171
+ # Returns a Hash of Resources stored by their name keys.
172
+ def resources
173
+ @resources ||= {}
174
+ end
175
+
176
+ # Store a Resource at the given key. A method is built for the
177
+ # instances with the same name as the key that calls the request method
178
+ # of the Resource.
179
+ #
180
+ # name - A Symbol name of the Resource and the eventual name of the
181
+ # method that will build the request.
182
+ # resource - The Resource object to store. When the named method is
183
+ # called on the client instance, this resource's request
184
+ # method will be called.
185
+ #
186
+ # Returns the stored Resource.
187
+ def []=(name,resource)
188
+ store name, resource
189
+ end
190
+
191
+ # A quick getter to retrieve a Resource from the client's
192
+ # internal store.
193
+ #
194
+ # name - The Symbol name of the Resource.
195
+ #
196
+ # Returns the Resource stored at name.
197
+ def [](name)
198
+ resources[name]
199
+ end
200
+
201
+ # Internal: Build a Rack router for the client's resources.
202
+ #
203
+ # Returns a Route object of the resources at the domain.
204
+ def route
205
+ Weary::Route.new resources.values, domain
206
+ end
207
+
208
+ # A Rack middleware interface that uses the internal router to
209
+ # determine the best Resource available.
210
+ #
211
+ # Returns an Array resembling a Rack response tuple.
212
+ def call(env)
213
+ route.call(env)
214
+ end
215
+
216
+ private
217
+
218
+ # Private: Store the resource in a hash keyed by the name (as a Symbol).
219
+ #
220
+ # Returns the saved Resource.
221
+ # Raises ArgumentError if you try to store anything but a Resource.
222
+ def store(name, resource)
223
+ raise ArgumentError, "Expected a Weary::Resource but got #{resource.inspect}" \
224
+ unless resource.is_a? Weary::Resource
225
+ key = name.to_sym
226
+ build_method(key, resource)
227
+ resources[key] = resource
228
+ end
229
+
230
+ # Private: Do the metaprogramming necessary for the resource names to
231
+ # become instance methods.
232
+ def build_method(key, resource)
233
+ define_method(key) do |*parameters, &block|
234
+ parameters = parameters.first || {}
235
+ @defaults ||= {}
236
+ request = resource.request(@defaults.merge(parameters), &block)
237
+ request
238
+ end
239
+ end
240
+ end
241
+
242
+ end
243
+ end