hyperclient 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use --create 1.9.3@hyperclient
1
+ rvm use --create 1.9.3-p125@hyperclient
data/Readme.md CHANGED
@@ -1,5 +1,5 @@
1
- # Hyperclient
2
- [![Build Status](https://secure.travis-ci.org/codegram/hyperclient.png)](http://travis-ci.org/codegram/hyperclient)
1
+ # Hyperclient
2
+ [![Build Status](https://secure.travis-ci.org/codegram/hyperclient.png)](http://travis-ci.org/codegram/hyperclient)
3
3
  [![Dependency Status](https://gemnasium.com/codegram/hyperclient.png)](http://gemnasium.com/codegram/hyperclient)
4
4
  [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/codegram/hyperclient)
5
5
 
@@ -30,7 +30,7 @@ Hyperclient only works with JSON HAL friendly APIs. [Learn about JSON HAL][hal].
30
30
 
31
31
  ## Resources
32
32
 
33
- Hyperclient will try to fetch and discover the resources from your API.
33
+ Hyperclient will try to fetch and discover the resources from your API.
34
34
 
35
35
  ### Links
36
36
 
@@ -111,6 +111,17 @@ posts = api.links.posts
111
111
  posts.post({title: "I'm a blogger!", body: 'Wohoo!!'})
112
112
  ````
113
113
 
114
+ If you have a templated link you can expand it like so:
115
+
116
+ ````ruby
117
+ api.links.post.expand(:id => 3).first
118
+ # => #<Resource ...>
119
+ ````
120
+
121
+ ## Other
122
+
123
+ There's also a PHP library named [HyperClient](https://github.com/FoxyCart/HyperClient), if that's what you were looking for :)
124
+
114
125
  ## TODO
115
126
 
116
127
  * Resource permissions: Using the `Allow` header Hyperclient should be able to
@@ -143,4 +154,4 @@ MIT License. Copyright 2012 [Codegram Technologies][codegram]
143
154
  [httparty]: http://github.com/jnunemaker/httparty
144
155
  [examples]: http://github.com/codegram/hyperclient/tree/master/examples
145
156
  [enumerable]: http://ruby-doc.org/core-1.9.3/Enumerable.html
146
- [rdoc]: http://rubydoc.org/github/codegram/hyperclient/master/frames
157
+ [rdoc]: http://rubydoc.org/github/codegram/hyperclient/master/frames
data/hyperclient.gemspec CHANGED
@@ -14,11 +14,13 @@ Gem::Specification.new do |gem|
14
14
  gem.require_paths = ["lib"]
15
15
  gem.version = Hyperclient::VERSION
16
16
 
17
- gem.add_dependency 'httparty'
17
+ gem.add_dependency 'faraday'
18
18
  gem.add_dependency 'uri_template'
19
+ gem.add_dependency 'net-http-digest_auth'
19
20
 
20
- gem.add_development_dependency 'minitest'
21
+ gem.add_development_dependency 'minitest', '~> 3.4.0'
21
22
  gem.add_development_dependency 'turn'
22
23
  gem.add_development_dependency 'webmock'
23
24
  gem.add_development_dependency 'mocha'
25
+ gem.add_development_dependency 'rack-test'
24
26
  end
@@ -34,6 +34,13 @@ module Hyperclient
34
34
  @collection[name.to_s]
35
35
  end
36
36
 
37
+ # Public: Returns the wrapped collection as a hash.
38
+ #
39
+ # Returns a Hash.
40
+ def to_hash
41
+ @collection.to_hash
42
+ end
43
+
37
44
  # Public: Provides method access to the collection values.
38
45
  #
39
46
  # It allows accessing a value as `collection.name` instead of
@@ -1,18 +1,12 @@
1
- require 'httparty'
1
+ require 'faraday'
2
2
  require 'json'
3
-
4
- # Public: A parser for HTTParty that understand the mime application/hal+json.
5
- class JSONHalParser < HTTParty::Parser
6
- SupportedFormats.merge!({'application/hal+json' => :json})
7
- end
3
+ require 'net/http/digest_auth'
8
4
 
9
5
  module Hyperclient
10
6
  # Internal: This class wrapps HTTParty and performs the HTTP requests for a
11
7
  # resource.
12
8
  class HTTP
13
- include HTTParty
14
- parser JSONHalParser
15
-
9
+ attr_writer :faraday
16
10
  # Public: Initializes the HTTP agent.
17
11
  #
18
12
  # url - A String to send the HTTP requests.
@@ -24,16 +18,19 @@ module Hyperclient
24
18
  # :user - A String with the user.
25
19
  # :password - A String with the user.
26
20
  # :debug - The flag (true/false) to debug the HTTP connections.
21
+ # :faraday_options - A Hash that will be passed to Faraday.new (optional).
22
+ # Can additionally include a :block => <Proc> that is also
23
+ # passed to Faraday.
27
24
  #
28
25
  def initialize(url, config)
29
26
  @url = url
30
27
  @config = config
31
28
  @base_uri = config.fetch(:base_uri)
29
+ @faraday_options = (config[:faraday_options] || {}).dup
30
+ @faraday_block = @faraday_options.delete(:block)
32
31
 
33
32
  authenticate!
34
33
  toggle_debug! if @config[:debug]
35
-
36
- self.class.headers(@config[:headers]) if @config.include?(:headers)
37
34
  end
38
35
 
39
36
  def url
@@ -48,7 +45,11 @@ module Hyperclient
48
45
  #
49
46
  # Returns: The parsed response.
50
47
  def get
51
- JSON.parse(self.class.get(url).response.body)
48
+ response = process_request :get
49
+
50
+ if body = response.body
51
+ JSON.parse(body)
52
+ end
52
53
  end
53
54
 
54
55
  # Public: Sends a POST request the the resource url.
@@ -57,7 +58,7 @@ module Hyperclient
57
58
  #
58
59
  # Returns: A HTTParty::Response
59
60
  def post(params)
60
- self.class.post(url, body: params)
61
+ wrap_response process_request(:post, params)
61
62
  end
62
63
 
63
64
  # Public: Sends a PUT request the the resource url.
@@ -66,31 +67,59 @@ module Hyperclient
66
67
  #
67
68
  # Returns: A HTTParty::Response
68
69
  def put(params)
69
- self.class.put(url, body: params)
70
+ wrap_response process_request(:put, params)
70
71
  end
71
72
 
72
73
  # Public: Sends an OPTIONS request the the resource url.
73
74
  #
74
75
  # Returns: A HTTParty::Response
75
76
  def options
76
- self.class.options(url)
77
+ wrap_response process_request(:options)
77
78
  end
78
79
 
79
80
  # Public: Sends a HEAD request the the resource url.
80
81
  #
81
82
  # Returns: A HTTParty::Response
82
83
  def head
83
- self.class.head(url)
84
+ wrap_response process_request(:head)
84
85
  end
85
86
 
86
87
  # Public: Sends a DELETE request the the resource url.
87
88
  #
88
89
  # Returns: A HTTParty::Response
89
90
  def delete
90
- self.class.delete(url)
91
+ wrap_response process_request(:delete)
91
92
  end
92
93
 
93
94
  private
95
+
96
+ def faraday
97
+ default_block = lambda do |faraday|
98
+ faraday.request :url_encoded
99
+ faraday.adapter :net_http
100
+ end
101
+ @faraday ||= Faraday.new({:url => @base_uri, :headers => @config[:headers] || {}
102
+ }.merge(@faraday_options), &(@faraday_block || default_block))
103
+ end
104
+
105
+ def wrap_response(faraday_response)
106
+ def faraday_response.code
107
+ status
108
+ end
109
+ faraday_response
110
+ end
111
+
112
+ def process_request(method, params = nil)
113
+ response = faraday.run_request method, url, params, faraday.headers
114
+ if response.status == 401 && @digest_auth
115
+ response = faraday.run_request method, url, nil, faraday.headers do |request|
116
+ request.headers['Authorization'] = digest_auth_header(
117
+ url, response.headers['www-authenticate'], method)
118
+ end
119
+ end
120
+ response
121
+ end
122
+
94
123
  # Internal: Sets the authentication method for HTTParty.
95
124
  #
96
125
  # options - An options Hash to set the authentication options.
@@ -98,21 +127,38 @@ module Hyperclient
98
127
  # Returns nothing.
99
128
  def authenticate!
100
129
  if (options = @config[:auth])
101
- auth_method = options.delete(:type).to_s + '_auth'
102
- self.class.send(auth_method, options[:user], options[:password])
130
+ auth_method = options.fetch(:type).to_s + '_auth'
131
+ send auth_method, options
103
132
  end
104
133
  end
105
134
 
135
+ def basic_auth(options)
136
+ faraday.basic_auth options[:user], options[:password]
137
+ end
138
+
139
+ def digest_auth(options)
140
+ @digest_auth = options
141
+ end
142
+
143
+ def digest_auth_header(url, realm, method)
144
+ uri = URI.parse(url)
145
+ uri.user = @digest_auth[:user]
146
+ uri.password = @digest_auth[:password]
147
+ digest_auth = Net::HTTP::DigestAuth.new
148
+ digest_auth.auth_header uri, realm, method.upcase
149
+ end
150
+
106
151
  # Internal: Enables HTTP debugging.
107
152
  #
108
- # stream - An object to stream the HTTP out to or just a truthy value.
153
+ # stream - An object to stream the HTTP out to or just a truthy value.
109
154
  def toggle_debug!
110
155
  stream = @config[:debug]
156
+ require 'logger'
111
157
 
112
158
  if stream.respond_to?(:<<)
113
- self.class.debug_output(stream)
159
+ faraday.response :logger, ::Logger.new(stream)
114
160
  else
115
- self.class.debug_output
161
+ faraday.response :logger, ::Logger.new($stderr)
116
162
  end
117
163
  end
118
164
  end
@@ -1,3 +1,3 @@
1
1
  module Hyperclient
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
@@ -34,6 +34,12 @@ module Hyperclient
34
34
 
35
35
  names.must_equal ['_links', 'title', 'description', 'permitted', '_embedded']
36
36
  end
37
+
38
+ describe '#to_hash' do
39
+ it 'returns the wrapped collection as a hash' do
40
+ collection.to_hash.must_be_kind_of Hash
41
+ end
42
+ end
37
43
  end
38
44
  end
39
45
 
@@ -14,6 +14,48 @@ module Hyperclient
14
14
  HTTP.new(url, config)
15
15
  end
16
16
 
17
+ describe 'initialize' do
18
+ it 'passes options to faraday' do
19
+ Faraday.expects(:new).with(:headers => {}, :url => config[:base_uri],
20
+ :x => :y).returns(stub('faraday', :headers => {},
21
+ :run_request => stub(:body => '{}', :status => 200)))
22
+
23
+ HTTP.new(url, config.merge(:faraday_options => {:x => :y})).get
24
+ end
25
+
26
+ it 'passes the options to faraday again when initializing it again' do
27
+ Faraday.expects(:new).with(:headers => {}, :url => config[:base_uri],
28
+ :x => :y).returns(stub('faraday', :headers => {},
29
+ :run_request => stub(:body => '{}', :status => 200))).times(2)
30
+
31
+ full_config = config.merge(:faraday_options => {:x => :y})
32
+ 2.times { HTTP.new(url, full_config).get }
33
+ end
34
+
35
+ it 'passes a block to faraday' do
36
+ app = stub('app')
37
+ http = HTTP.new(url, config.merge(
38
+ :faraday_options => {:block => lambda{|f| f.adapter :rack, app}}))
39
+
40
+ app.expects(:call).returns([200, {}, '{}'] )
41
+
42
+ http.get
43
+ end
44
+
45
+ it 'passes a block to faraday again when initializing again' do
46
+ app = stub('app')
47
+
48
+ app.expects(:call).returns([200, {}, '{}'] ).times(2)
49
+
50
+ full_config = config.merge(:faraday_options => {:block => lambda{|f|
51
+ f.adapter :rack, app}})
52
+ 2.times {
53
+ http = HTTP.new(url, full_config)
54
+ http.get
55
+ }
56
+ end
57
+ end
58
+
17
59
  describe 'url' do
18
60
  it 'merges the resource url with the base uri' do
19
61
  http.url.to_s.must_equal 'http://api.example.org/productions/1'
@@ -27,7 +69,7 @@ module Hyperclient
27
69
  end
28
70
 
29
71
  describe 'authentication' do
30
- it 'sets the authentication options' do
72
+ it 'sets the basic authentication options' do
31
73
  stub_request(:get, 'http://user:pass@api.example.org/productions/1').
32
74
  to_return(body: '{"resource": "This is the resource"}')
33
75
 
@@ -35,6 +77,19 @@ module Hyperclient
35
77
 
36
78
  http.get.must_equal({'resource' => 'This is the resource'})
37
79
  end
80
+
81
+ it 'sets the digest authentication options' do
82
+ stub_request(:get, 'http://api.example.org/productions/1').
83
+ to_return(status: 401, headers: {'www-authenticate' => 'private area'})
84
+ stub_request(:get, 'http://api.example.org/productions/1').
85
+ with(headers: {'Authorization' =>
86
+ %r{Digest username="user", realm="", algorithm=MD5, uri="/productions/1"}}).
87
+ to_return(body: '{"resource": "This is the resource"}')
88
+
89
+ config.update({auth: {type: :digest, user: 'user', password: 'pass'}})
90
+
91
+ http.get.must_equal({'resource' => 'This is the resource'})
92
+ end
38
93
  end
39
94
 
40
95
  describe 'headers' do
@@ -50,17 +105,32 @@ module Hyperclient
50
105
  end
51
106
 
52
107
  describe 'debug' do
108
+ before(:each) do
109
+ @stderr = $stderr
110
+ stub_request(:get, 'http://api.example.org/productions/1').
111
+ to_return(body: '{"resource": "This is the resource"}')
112
+ end
113
+
114
+ after(:each) do
115
+ $stderr = @stderr
116
+ end
117
+
53
118
  it 'enables debugging' do
119
+ $stderr = StringIO.new
54
120
  config.update({debug: true})
55
121
 
56
- http.class.instance_variable_get(:@default_options)[:debug_output].must_equal $stderr
122
+ http.get
123
+
124
+ $stderr.string.must_include('get http://api.example.org/productions/1')
57
125
  end
58
126
 
59
127
  it 'uses a custom stream' do
60
128
  stream = StringIO.new
61
129
  config.update({debug: stream})
62
130
 
63
- http.class.instance_variable_get(:@default_options)[:debug_output].must_equal stream
131
+ http.get
132
+
133
+ stream.string.must_include('get http://api.example.org/productions/1')
64
134
  end
65
135
  end
66
136
 
@@ -78,6 +148,13 @@ module Hyperclient
78
148
 
79
149
  http.get.must_equal({'some_json' => 12345})
80
150
  end
151
+
152
+ it 'returns nil if the response body is nil' do
153
+ stub_request(:get, 'http://api.example.org/productions/1').
154
+ to_return(body: nil)
155
+
156
+ http.get.must_equal(nil)
157
+ end
81
158
  end
82
159
 
83
160
  describe 'post' do
metadata CHANGED
@@ -1,112 +1,144 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyperclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
5
4
  prerelease:
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Oriol Gual
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-23 00:00:00.000000000 Z
12
+ date: 2012-12-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: httparty
16
- requirement: !ruby/object:Gem::Requirement
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
17
20
  none: false
21
+ prerelease: false
22
+ name: faraday
23
+ requirement: !ruby/object:Gem::Requirement
18
24
  requirements:
19
25
  - - ! '>='
20
26
  - !ruby/object:Gem::Version
21
27
  version: '0'
28
+ none: false
22
29
  type: :runtime
23
- prerelease: false
30
+ - !ruby/object:Gem::Dependency
24
31
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
32
  requirements:
27
33
  - - ! '>='
28
34
  - !ruby/object:Gem::Version
29
35
  version: '0'
30
- - !ruby/object:Gem::Dependency
36
+ none: false
37
+ prerelease: false
31
38
  name: uri_template
32
39
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
40
  requirements:
35
41
  - - ! '>='
36
42
  - !ruby/object:Gem::Version
37
43
  version: '0'
44
+ none: false
38
45
  type: :runtime
39
- prerelease: false
46
+ - !ruby/object:Gem::Dependency
40
47
  version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
41
52
  none: false
53
+ prerelease: false
54
+ name: net-http-digest_auth
55
+ requirement: !ruby/object:Gem::Requirement
42
56
  requirements:
43
57
  - - ! '>='
44
58
  - !ruby/object:Gem::Version
45
59
  version: '0'
60
+ none: false
61
+ type: :runtime
46
62
  - !ruby/object:Gem::Dependency
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: 3.4.0
68
+ none: false
69
+ prerelease: false
47
70
  name: minitest
48
71
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
72
  requirements:
51
- - - ! '>='
73
+ - - ~>
52
74
  - !ruby/object:Gem::Version
53
- version: '0'
75
+ version: 3.4.0
76
+ none: false
54
77
  type: :development
55
- prerelease: false
78
+ - !ruby/object:Gem::Dependency
56
79
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
80
  requirements:
59
81
  - - ! '>='
60
82
  - !ruby/object:Gem::Version
61
83
  version: '0'
62
- - !ruby/object:Gem::Dependency
84
+ none: false
85
+ prerelease: false
63
86
  name: turn
64
87
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
88
  requirements:
67
89
  - - ! '>='
68
90
  - !ruby/object:Gem::Version
69
91
  version: '0'
92
+ none: false
70
93
  type: :development
71
- prerelease: false
94
+ - !ruby/object:Gem::Dependency
72
95
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
96
  requirements:
75
97
  - - ! '>='
76
98
  - !ruby/object:Gem::Version
77
99
  version: '0'
78
- - !ruby/object:Gem::Dependency
100
+ none: false
101
+ prerelease: false
79
102
  name: webmock
80
103
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
104
  requirements:
83
105
  - - ! '>='
84
106
  - !ruby/object:Gem::Version
85
107
  version: '0'
108
+ none: false
86
109
  type: :development
87
- prerelease: false
110
+ - !ruby/object:Gem::Dependency
88
111
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
112
  requirements:
91
113
  - - ! '>='
92
114
  - !ruby/object:Gem::Version
93
115
  version: '0'
94
- - !ruby/object:Gem::Dependency
116
+ none: false
117
+ prerelease: false
95
118
  name: mocha
96
119
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
120
  requirements:
99
121
  - - ! '>='
100
122
  - !ruby/object:Gem::Version
101
123
  version: '0'
124
+ none: false
102
125
  type: :development
103
- prerelease: false
126
+ - !ruby/object:Gem::Dependency
104
127
  version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
105
132
  none: false
133
+ prerelease: false
134
+ name: rack-test
135
+ requirement: !ruby/object:Gem::Requirement
106
136
  requirements:
107
137
  - - ! '>='
108
138
  - !ruby/object:Gem::Version
109
139
  version: '0'
140
+ none: false
141
+ type: :development
110
142
  description: HyperClient is a Ruby Hypermedia API client.
111
143
  email:
112
144
  - oriol.gual@gmail.com
@@ -156,26 +188,26 @@ rdoc_options: []
156
188
  require_paths:
157
189
  - lib
158
190
  required_ruby_version: !ruby/object:Gem::Requirement
159
- none: false
160
191
  requirements:
161
192
  - - ! '>='
162
193
  - !ruby/object:Gem::Version
163
194
  version: '0'
164
195
  segments:
165
196
  - 0
166
- hash: -1184667501328950554
167
- required_rubygems_version: !ruby/object:Gem::Requirement
197
+ hash: -3503842849478118704
168
198
  none: false
199
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
200
  requirements:
170
201
  - - ! '>='
171
202
  - !ruby/object:Gem::Version
172
203
  version: '0'
173
204
  segments:
174
205
  - 0
175
- hash: -1184667501328950554
206
+ hash: -3503842849478118704
207
+ none: false
176
208
  requirements: []
177
209
  rubyforge_project:
178
- rubygems_version: 1.8.23
210
+ rubygems_version: 1.8.24
179
211
  signing_key:
180
212
  specification_version: 3
181
213
  summary: ''