weary 0.7.2 → 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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