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
@@ -1,110 +1,59 @@
1
+ require 'rack/response'
2
+
3
+ autoload :MultiJson, 'multi_json'
4
+
1
5
  module Weary
2
6
  class Response
3
-
4
- attr_reader :raw, :requester, :code, :message, :header, :content_type, :cookie, :body, :url
5
-
6
- def initialize(http_response, requester)
7
- @raw = http_response
8
- @requester = requester
9
- @url = requester.uri
10
- @code = http_response.code.to_i
11
- @message = http_response.message
12
- @header = http_response.to_hash
13
- @content_type = http_response.content_type
14
- @cookie = http_response['Set-Cookie']
15
- @body = http_response.body
7
+ include Rack::Response::Helpers
8
+
9
+ def initialize(body, status, headers)
10
+ @response = Rack::Response.new body, status, headers
11
+ @status = self.status
12
+ end
13
+
14
+ def status
15
+ @response.status.to_i
16
+ end
17
+
18
+ def header
19
+ @response.header
20
+ end
21
+ alias headers header
22
+
23
+ def body
24
+ buffer = ""
25
+ @response.body.each {|chunk| buffer << chunk }
26
+ buffer
27
+ end
28
+
29
+ def each(&iterator)
30
+ @response.body.each(&iterator)
31
+ end
32
+
33
+ def finish
34
+ [status, header, self]
35
+ end
36
+
37
+ def success?
38
+ @response.successful?
16
39
  end
17
-
18
- # Is this an HTTP redirect?
40
+
19
41
  def redirected?
20
- raw.is_a? Net::HTTPRedirection
21
- end
22
-
23
- # Was this Request successful?
24
- def success?
25
- (200..299).include?(code)
42
+ @response.redirection?
26
43
  end
27
-
28
- # Returns a symbol corresponding to the Response's Content Type
29
- def format
30
- @format ||= case content_type
31
- when *ContentTypes[:json]
32
- :json
33
- when *ContentTypes[:xml]
34
- :xml
35
- when *ContentTypes[:html]
36
- :html
37
- when *ContentTypes[:yaml]
38
- :yaml
39
- else
40
- :plain
41
- end
44
+
45
+ def length
46
+ @response.length
42
47
  end
43
-
44
- # Follow the Redirect
45
- def follow_redirect
46
- if redirected?
47
- new_request = requester.dup
48
- new_request.uri = @raw['location']
49
- new_request.perform
50
- end
48
+
49
+ def call(env)
50
+ self.finish
51
51
  end
52
-
53
- # Parse the body with Crack parsers (if XML/HTML) or Yaml parser
52
+
54
53
  def parse
55
- raise StandardError, "The Response has no body. #{requester.via.to_s.upcase} request sent." unless body
56
- handle_errors
57
- case format
58
- when :xml, :html
59
- Crack::XML.parse body
60
- when :json
61
- Crack::JSON.parse body
62
- when :yaml
63
- YAML::load body
64
- else
65
- body
66
- end
67
- end
68
-
69
- # Puts the body
70
- def to_s
71
- body.to_s
72
- end
73
-
74
- # Same as parse[key]
75
- def [](key)
76
- parse[key]
54
+ raise "The response does not contain a body" if body.nil? || body.empty?
55
+ raise "Unable to parse Content-Type: #{content_type}" unless content_type =~ /json($|;.*)/
56
+ MultiJson.decode body
77
57
  end
78
-
79
- private
80
- def handle_errors
81
- case @code
82
- when 301,302
83
- raise RedirectionError, "#{@message} to #{@raw['location']}"
84
- when 200...400
85
- return
86
- when 400
87
- raise BadRequest, "Failed with #{@code}: #{@message}"
88
- when 401
89
- raise Unauthorized, "Failed with #{@code}: #{@message}"
90
- when 403
91
- raise Forbidden, "Failed with #{@code}: #{@message}"
92
- when 404
93
- raise NotFound, "Failed with #{@code}: #{@message}"
94
- when 405
95
- raise MethodNotAllowed, "Failed with #{@code}: #{@message}"
96
- when 409
97
- raise ResourceConflict, "Failed with #{@code}: #{@message}"
98
- when 422
99
- raise UnprocessableEntity, "Failed with #{@code}: #{@message}"
100
- when 401...500
101
- raise ClientError, "Failed with #{@code}: #{@message}"
102
- when 500...600
103
- raise ServerError, "Failed with #{@code}: #{@message}"
104
- else
105
- raise HTTPError, "Unknown response code: #{@code}"
106
- end
107
- end
108
-
109
58
  end
110
59
  end
@@ -0,0 +1,53 @@
1
+ require 'rack'
2
+
3
+ module Weary
4
+ class Route
5
+ NotFoundError = Class.new(StandardError)
6
+ NotAllowedError = Class.new(StandardError)
7
+
8
+ def initialize(resources, domain="")
9
+ @resources = resources
10
+ @domain = domain
11
+ end
12
+
13
+ def call(env)
14
+ begin
15
+ request = Rack::Request.new(env)
16
+ resource = route(request)
17
+ url_variables = resource.url.extract("#{@domain}#{request.path}")
18
+ resource.request(url_variables.merge(request.params)).call(env)
19
+ rescue NotFoundError => e
20
+ [404, {'Content-Type' => "text/plain"}, [e.message]]
21
+ rescue NotAllowedError => e
22
+ [405, {'Content-Type' => "text/plain"}, [e.message]]
23
+ rescue Weary::Resource::UnmetRequirementsError => e
24
+ [403, {'Content-Type' => "text/plain"}, [e.message]]
25
+ rescue Exception => e
26
+ [500, {'Content-Type' => "text/plain"}, [e.message]]
27
+ end
28
+ end
29
+
30
+ def route(request)
31
+ subset = select_by_url("#{@domain}#{request.path}")
32
+ raise NotFoundError, "Not Found" if subset.empty?
33
+ subset = select_by_method(request.request_method, subset)
34
+ raise NotAllowedError, "Method Not Allowed" if subset.empty?
35
+ subset.first
36
+ end
37
+
38
+ private
39
+
40
+ def select_by_url(url, set=@resources)
41
+ set.select do |resource|
42
+ !resource.url.extract(url).nil?
43
+ end
44
+ end
45
+
46
+ def select_by_method(method, set=@resources)
47
+ set.select do |resource|
48
+ resource.method.to_s.upcase == method
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Weary
2
+ VERSION = "1.0.0.rc1"
3
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,58 +1,6 @@
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
1
+ require 'weary'
2
+ require 'webmock/rspec'
10
3
 
11
- begin
12
- require 'weary'
13
- rescue LoadError
14
- lib_path = File.join(File.dirname(__FILE__), '..', 'lib')
15
- $LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
16
- require 'weary'
17
- end
4
+ Dir['./spec/support/**/*.rb'].each {|f| require f }
18
5
 
19
- require 'spec'
20
- require 'fakeweb'
21
-
22
- def get_fixture(filename)
23
- open(File.join(File.dirname(__FILE__), 'fixtures', "#{filename.to_s}")).read
24
- end
25
-
26
- def http_status_message(code)
27
- message = case code.to_i
28
- when 200
29
- "OK"
30
- when 301
31
- "Moved Permanently"
32
- when 302
33
- "Moved Temporarily"
34
- when 400
35
- "Bad Request"
36
- when 401
37
- "Unauthorized"
38
- when 403
39
- "Forbidden"
40
- when 404
41
- "Not Found"
42
- when 405
43
- "Method Not Allowed"
44
- when 409
45
- "Conflict"
46
- when 418
47
- "I'm a teapot"
48
- when 422
49
- "Unprocessable Entity"
50
- when 401...500
51
- "Client Error"
52
- when 500...600
53
- "Server Error"
54
- else
55
- "Unknown"
56
- end
57
- [code.to_s,message]
58
- end
6
+ WebMock.disable_net_connect!
@@ -0,0 +1,70 @@
1
+ shared_examples_for "a Rack application" do
2
+ it { should respond_to :call }
3
+
4
+ it "takes one argument, the environment" do
5
+ method = if subject.is_a? Module
6
+ subject.method :call
7
+ else
8
+ subject.class.instance_method :call
9
+ end
10
+ method.arity.should eq 1
11
+ end
12
+
13
+ it "returns an Array" do
14
+ subject.call(env).should be_an Array
15
+ end
16
+
17
+ it "returns with three arguments" do
18
+ subject.call(env).length.should be 3
19
+ end
20
+
21
+ describe "the status" do
22
+ let(:status) { subject.call(env).first }
23
+
24
+ it "is greater than or equal to 100 when parsed as an integer" do
25
+ status.to_i.should be >= 100
26
+ end
27
+ end
28
+
29
+ describe "the headers" do
30
+ let(:headers) { subject.call(env)[1] }
31
+
32
+ it { headers.should respond_to :each }
33
+
34
+ it "yields values of key and value" do
35
+ headers.should be_all {|key, value| !key.nil? && !value.nil? }
36
+ end
37
+
38
+ it "contains keys of Strings" do
39
+ headers.keys.should be_all {|key| key.kind_of? String }
40
+ end
41
+
42
+ it "does not contain a 'Status' key" do
43
+ headers.keys.map(&:downcase).should_not include 'status'
44
+ end
45
+
46
+ it "contains values of Strings" do
47
+ headers.values.should be_all {|value| value.kind_of? String }
48
+ end
49
+
50
+ it "contains a 'Content-Type' header" do
51
+ headers.should have_key 'Content-Type'
52
+ end
53
+ end
54
+
55
+ describe "the body" do
56
+ let(:body) { subject.call(env).last }
57
+
58
+ it { body.should respond_to :each }
59
+
60
+ it "yields only String values" do
61
+ map = []
62
+ body.each {|string| map << string }
63
+ # We do this strange mapping, since the only requirement
64
+ # of the body is it responds to :each.
65
+ # This allows us to use Array predicates
66
+ map.should be_all {|i| i.kind_of? String }
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,14 @@
1
+ shared_examples_for "a Rack env" do
2
+ # From the Rack spec:
3
+ # http://rack.rubyforge.org/doc/SPEC.html
4
+ it { should be_an_instance_of Hash }
5
+ it { should have_key "REQUEST_METHOD"}
6
+ it "does not have an empty value for REQUEST_METHOD" do
7
+ subject["REQUEST_METHOD"].should_not be_empty
8
+ end
9
+ it { should have_key "SCRIPT_NAME"}
10
+ it { should have_key "PATH_INFO"}
11
+ it { should have_key "QUERY_STRING"}
12
+ it { should have_key "SERVER_NAME"}
13
+ it { should have_key "SERVER_PORT"}
14
+ end
@@ -0,0 +1,9 @@
1
+ shared_examples_for "a URI" do
2
+ it { uri.should respond_to(:path) }
3
+ it { uri.should respond_to(:query) }
4
+ it { uri.should respond_to(:host) }
5
+ it { uri.should respond_to(:port) }
6
+ it { uri.should respond_to(:inferred_port) }
7
+ it { uri.should respond_to(:request_uri) }
8
+ it { uri.should respond_to(:scheme) }
9
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Weary::Adapter do
4
+ subject { Class.new { include Weary::Adapter }.new }
5
+ let(:env) {
6
+ {
7
+ "REQUEST_METHOD"=>"GET",
8
+ "SCRIPT_NAME"=>"",
9
+ "PATH_INFO"=>"/api/v2/json/repos/show/mwunsch/weary",
10
+ "QUERY_STRING"=>nil,
11
+ "SERVER_NAME"=>"github.com",
12
+ "SERVER_PORT"=>80,
13
+ "REQUEST_URI"=>"/api/v2/json/repos/show/mwunsch/weary"
14
+ }
15
+ }
16
+
17
+ describe "#call" do
18
+ it_behaves_like "a Rack application"
19
+ end
20
+
21
+ describe "connect" do
22
+ it "returns a Rack::Response" do
23
+ subject.connect(Rack::Request.new(env)).should be_a Rack::Response
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe Weary::Adapter::NetHttp do
4
+ before do
5
+ @url = "http://github.com/api/v2/json/repos/show/mwunsch/weary"
6
+ @request = Weary::Request.new @url
7
+ end
8
+
9
+ describe ".call" do
10
+ before do
11
+ stub_request(:get, @url).
12
+ to_return(:status => 200, :body => "", :headers => {})
13
+ end
14
+
15
+ it_behaves_like "a Rack application" do
16
+ subject { described_class }
17
+ let(:env) { @request.env }
18
+ end
19
+
20
+ it "performs the request through the connect method" do
21
+ described_class.stub(:connect) { Rack::Response.new("", 200, {})}
22
+ described_class.should_receive :connect
23
+ described_class.call(@request.env)
24
+ end
25
+ end
26
+
27
+ describe "#call" do
28
+ it "calls the class method `.call`" do
29
+ described_class.stub(:call) { [200, {'Content-Type' => 'text/plain'}, [""]] }
30
+ described_class.should_receive(:call)
31
+ described_class.new.call(@request.env)
32
+ end
33
+ end
34
+
35
+ describe ".connect" do
36
+ before do
37
+ stub_request(:get, @url)
38
+ end
39
+
40
+ it "performs the http request" do
41
+ req = Rack::Request.new(@request.env)
42
+ described_class.connect(req)
43
+ a_request(:get, @url).should have_been_made
44
+ end
45
+
46
+ it "returns a Rack::Response" do
47
+ req = Rack::Request.new(@request.env)
48
+ described_class.connect(req).should be_kind_of Rack::Response
49
+ end
50
+
51
+ it "sets appropriate request headers" do
52
+ @request.headers 'User-Agent' => Weary::USER_AGENTS['Lynx 2.8.4rel.1 on Linux']
53
+ req = Rack::Request.new(@request.env)
54
+ described_class.connect(req)
55
+ a_request(:get, @url).with(:headers => @request.headers).should have_been_made
56
+ end
57
+
58
+ it "sets the body of the request" do
59
+ stub_request(:post, @url)
60
+ @request.method = "POST"
61
+ @request.params :foo => "baz"
62
+ req = Rack::Request.new(@request.env)
63
+ described_class.connect(req)
64
+ a_request(:post, @url).with(:body => "foo=baz").should have_been_made
65
+ end
66
+ end
67
+
68
+ describe ".normalize_request_headers" do
69
+ it "removes the HTTP_ prefix from request headers" do
70
+ @request.headers 'User-Agent' => Weary::USER_AGENTS['Lynx 2.8.4rel.1 on Linux']
71
+ headers = described_class.normalize_request_headers(@request.env)
72
+ headers.should have_key "User-Agent"
73
+ end
74
+ end
75
+
76
+ describe ".socket" do
77
+ it "sets up the http connection" do
78
+ req = Rack::Request.new(@request.env)
79
+ described_class.socket(req).should be_kind_of Net::HTTP
80
+ end
81
+ end
82
+
83
+ describe ".request_class" do
84
+ it "gets the Net::HTTP request class for the request method" do
85
+ described_class.request_class("POST").should be Net::HTTP::Post
86
+ end
87
+ end
88
+ end