hyperclient 0.1.0 → 0.2.0

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.
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: ''