lelylan-rb 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.0.4 (January 26, 2013)
4
+
5
+ * Full support to error handling
6
+ * Fixed physical device request
7
+ * Fixed HTTP Basic Authentication system for subscription services
8
+ * Made some extra tests to check the fact that delete services are
9
+ correctly working
10
+
3
11
  ## v0.0.3 (January 25, 2013)
4
12
 
5
13
  * Added content lenght 0 for all DELETE requests
data/README.md CHANGED
@@ -53,8 +53,10 @@ related documentation in the [dev center](http://dev.lelylan.com/api/oauth#langu
53
53
  oauth = OAuth2::Client.new(client_id, client_secret, site: site)
54
54
 
55
55
  # Redirect the application to the Lelylan authorization page
56
- redirect oauth.auth_code.authorize_url(redirect_uri: redirect_uri)
57
- # => http://people.lelylan.com/oauth/authorize?redirect_uri=http://localhost:3000/callback&scope=devices&response_type=code&client_id=<client-id>
56
+ redirect oauth.auth_code.authorize_url(redirect_uri: redirect_uri, scope: scope)
57
+ # => http://people.lelylan.com/oauth/authorize?
58
+ # redirect_uri=http://localhost:3000/callback&
59
+ #  scope=<scope>&response_type=code&client_id=<client-id>
58
60
 
59
61
  # Get the access token object (authorization code is given from the previous step)
60
62
  token = oauth.auth_code.get_token(params[:code], redirect_uri: redirect_uri)
@@ -69,8 +71,12 @@ following example shows how to print in the console a list of owned devices.
69
71
  # Initialize Lelylan client
70
72
  lelylan = Lelylan::Client.new(token: token)
71
73
 
72
- # Get the first device where the name matches with Dimmer
74
+ # Get the first device where the name matches with Dimmer.
73
75
  device = lelylan.devices(name: 'Dimmer').first
76
+
77
+ # The client returns an Hashie (https://github.com/intridea/hashie)
78
+ puts device.uri # get the device uri
79
+ puts device.properties.first.value # get the first device property value
74
80
  ```
75
81
 
76
82
  ### Realtime services
@@ -83,106 +89,87 @@ lelylan = Lelylan::Client.new(client_id:'<client-id>', client_secret: '<client-s
83
89
  subscriptions = lelylan.subscriptions
84
90
  ```
85
91
 
86
- ## Authorization flows
87
-
88
- Lelylan support three OAuth2 authorization flows.
89
-
90
- ### Authorization code flows
91
-
92
- ```ruby
93
- oauth = OAuth2::Client.new(client_id, client_secret, site: site)
94
- redirect oauth.auth_code.authorize_url(redirect_uri: redirect_uri)
95
- token = oauth.auth_code.get_token(params[:code], redirect_uri: redirect_uri)
96
- ```
97
-
98
- ### Implicit grant flow
99
-
100
- ```ruby
101
- oauth = OAuth2::Client.new(client_id, client_secret, site: site)
102
- redirect oauth.auth_code.authorize_url(redirect_uri: redirect_uri)
103
- token = OAuth2::AccessToken.from_kvform(client, params)
104
- ```
92
+ ### Implemented Services
105
93
 
106
- ### Resource owner password credentials flow
107
-
108
- ```ruby
109
- oauth = OAuth2::Client.new(client_id, client_secret, site: site)
110
- token = oauth.password.get_token('email', 'password')
111
- ```
112
-
113
- Access tokens, when expired, are automatically refreshed.
114
-
115
-
116
- ## Lelylan Services
117
-
118
- ### Devices
119
-
120
- The Device API defines a set of services to monitor and control every existing device.
121
- Its final goal is to map every device to a unique URI which provides control over it.
94
+ **Devices** - The Device API defines a set of services to monitor and control every existing
95
+ device. Its final goal is to map every device to a unique URI which provides control over it.
122
96
  [See examples](http://dev.lelylan.com/api/devices#ruby).
123
97
 
124
- ### Histories
98
+ **Activations** - Easy way to move the device ownership between people.
99
+ [See examples](http://dev.lelylan.com/api/devices#ruby).
125
100
 
126
- When a device updates its properties or executes a function a new history resource with
127
- a snapshot of all device properties is created by Lelylan, also the ones that has not been
128
- updated. This makes it easy to recreate previous device status and extract usage patterns
129
- to improve the way people live their house.
101
+ **Histories** - When a device updates its properties or executes a function a new history
102
+ resource with a snapshot of all device properties is created by Lelylan, also the ones that
103
+ has not been updated. This makes it easy to recreate previous device status and extract usage
104
+ patterns to improve the way people live their house.
130
105
  [See examples](http://dev.lelylan.com/api/devices/histories#ruby).
131
106
 
132
- ### Types
133
-
134
- A type describes the structure of a device. In its simplest form every type can be defined
135
- as the combination of three key elements: properties (what vary during time), functions
107
+ **Types** - A type describes the structure of a device. In its simplest form every type can be
108
+ defined as the combination of three key elements: properties (what vary during time), functions
136
109
  (what a device can do), statuses (what a device is in a specific time of its life).
137
110
  [See examples](http://dev.lelylan.com/api/types#ruby).
138
111
 
139
- ### Properties
140
-
141
- A property is whatever vary in a device during time. It can be the intensity in a dimmer,
142
- the temperature in a cooling system or the volume in a television.
112
+ **Properties** - A property is whatever vary in a device during time. It can be the intensity in
113
+ a dimmer, the temperature in a cooling system or the volume in a television.
143
114
  [See examples](http://dev.lelylan.com/api/types/properties#ruby).
144
115
 
145
- ### Functions
146
-
147
- Functions defines the daily interactions you have with the devices in your house, for
148
- example when you turn on a light, close a door or raise the temperature in a room.
116
+ **Functions** - Functions defines the daily interactions you have with the devices in your house,
117
+ for example when you turn on a light, close a door or raise the temperature in a room.
149
118
  With functions you can control any device in the same way you do everyday of your life.
150
119
  [See examples](http://dev.lelylan.com/api/types/functions#ruby).
151
120
 
152
- ### Statuses
153
-
154
- Properties are not always enough to describe the status of a device. Think at a roller
121
+ **Statuses** - Properties are not always enough to describe the status of a device. Think at a roller
155
122
  shutter for example. It has the property aperture that is 100 when open or 0 when closed.
156
123
  But what if the roller shutter is opening? It is nether open or close. To have a complete
157
124
  control over the device status in a specific moment of its life is to use the status API.
158
125
  [See examples](http://dev.lelylan.com/api/types/statuses#ruby).
159
126
 
160
- ### Locations
161
-
162
- Locations are the places we live in and where physical devices are placed. Lelylan identifies
127
+ **Locations** - Locations are the places we live in and where physical devices are placed. Lelylan identifies
163
128
  three types of locations usually organized in a hierarchical structure: houses, floors and
164
129
  rooms.
165
130
  [See examples](http://dev.lelylan.com/api/locations#ruby).
166
131
 
167
- ### Physical devices
168
-
169
- Physical devices are the real objects you physically interact with everyday of your life
132
+ **Physical Devices** - Physical devices are the real objects you physically interact with everyday of your life
170
133
  like lights, appliances, alarms and more. To enable the communication between Lelylan and
171
134
  physical devices they should provide a simple set of web services.
172
135
  [See examples](http://dev.lelylan.com/api/physicals#ruby).
173
136
 
174
- ### Subscriptions
175
-
176
- Get real-time updates by subscribing to a resource and its related event.
137
+ **Subscriptions** - Get realtime updates by subscribing to a resource and its related event.
177
138
  [See examples](http://dev.lelylan.com/api/realtime#ruby).
178
139
 
179
- ### Authenticated User Profile
180
-
181
- Returns extended information for the authenticated user.
140
+ **User Profile** - Returns extended information for the authenticated user.
182
141
  [See examples](http://dev.lelylan.com/api/core#get-a-user-ruby).
183
142
 
184
143
 
185
- ## Errors
144
+ ### Authorization flows
145
+
146
+ #### Authorization code flows
147
+
148
+ ```ruby
149
+ oauth = OAuth2::Client.new(client_id, client_secret, site: site)
150
+ redirect oauth.auth_code.authorize_url(redirect_uri: redirect_uri)
151
+ token = oauth.auth_code.get_token(params[:code], redirect_uri: redirect_uri)
152
+ ```
153
+
154
+ #### Implicit grant flow
155
+
156
+ ```ruby
157
+ oauth = OAuth2::Client.new(client_id, client_secret, site: site)
158
+ redirect oauth.auth_code.authorize_url(redirect_uri: redirect_uri)
159
+ token = OAuth2::AccessToken.from_kvform(client, params)
160
+ ```
161
+
162
+ #### Resource owner password credentials flow
163
+
164
+ ```ruby
165
+ oauth = OAuth2::Client.new(client_id, client_secret, site: site)
166
+ token = oauth.password.get_token('email', 'password')
167
+ ```
168
+
169
+ Access tokens, when expired, are automatically refreshed.
170
+
171
+
172
+ ### Errors
186
173
 
187
174
  Exceptions are raised when a 4xx or 5xx status code is returned.
188
175
 
@@ -199,18 +186,21 @@ Through the error message attribute you can access the error information.
199
186
 
200
187
  ```ruby
201
188
  begin
202
- @type = Lelylan::Type.type("https://type.lelylan.com/types/wrong")
189
+ device = lelylan.device('<id>')
203
190
  rescue Lelylan::Error => e
204
- puts "The resource #{e.message.error.uri} was not found"
191
+ puts e.message
205
192
  end
206
193
  ```
207
194
 
195
+ Unluckily the `#message` method can only be a string. For this reason we
196
+ can't return a JSON structure when lelylan offers it, but we return the
197
+ `error.description` value.
208
198
  Learn more about the [error response structure](http://dev.lelylan.com/api/core#errors).
209
199
 
210
200
 
211
- ## Configurations
201
+ ### Configurations
212
202
 
213
- ### API endpoint
203
+ #### API endpoint
214
204
 
215
205
  Configuration block.
216
206
 
@@ -242,24 +232,34 @@ provide specs to your contribution.
242
232
  * Run `bundle install` for dependencies.
243
233
  * Run `bundle exec guard` and press enter to execute all specs.
244
234
 
235
+ ### Running locally
236
+
237
+ Whenever you want to use the source code from your IRB session simply import `lib/`.
238
+
239
+ ```
240
+ $ git clone https://github.com/lelylan/lelylan-rb
241
+ $ cd lelylan-rb
242
+ $ irb -I lib/
243
+ $ > require 'lelylan'
244
+ ```
245
245
 
246
- ## Spec guidelines
246
+ ### Spec guidelines
247
247
 
248
248
  Follow [rspec best practices](http://betterspecs.org) guidelines.
249
249
 
250
250
 
251
- ## Coding guidelines
251
+ ### Coding guidelines
252
252
 
253
253
  Follow [github](https://github.com/styleguide/) guidelines.
254
254
 
255
255
 
256
- ## Feedback
256
+ ### Feedback
257
257
 
258
258
  Use the [issue tracker](http://github.com/lelylan/lelylan-rb/issues) for bugs.
259
259
  [Mail](mailto:touch@lelylan.com) or [Tweet](http://twitter.com/lelylan) us for any idea that can improve the project.
260
260
 
261
261
 
262
- ## Links
262
+ ### Links
263
263
 
264
264
  * [GIT Repository](http://github.com/lelylan/lelylan-rb)
265
265
  * [Lelylan Ruby Website](http://lelylan.github.com/lelylan-rb).
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- require File.expand_path('../lib/lelylan/version', __FILE__)
2
+ require File.expand_path('./../lib/lelylan/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
5
 
@@ -1,5 +1,5 @@
1
1
  require 'faraday'
2
- require 'multi_json'
2
+ require 'hashie'
3
3
 
4
4
  module Faraday
5
5
  class Response::RaiseHttpError < Response::Middleware
@@ -29,7 +29,14 @@ module Faraday
29
29
  end
30
30
 
31
31
  def error_message(response)
32
- response[:body]
32
+ body = response[:body] || ''
33
+
34
+ begin
35
+ body = Hashie::Mash.new(JSON.parse(response[:body]))
36
+ rescue
37
+ end
38
+
39
+ body.is_a?(::Hashie::Mash) ? body.error.description : body
33
40
  end
34
41
  end
35
42
  end
@@ -23,10 +23,18 @@ module Lelylan
23
23
 
24
24
  digest = OpenSSL::Digest::Digest.new('sha1')
25
25
  signature = OpenSSL::HMAC.hexdigest(digest, secret, params.to_json.to_s)
26
- headers = { 'X-Physical-Signature' => signature, 'Content-Type' => 'application/json' }
26
+ headers = { 'X-Physical-Signature' => signature }
27
+
28
+ request = Faraday.new do |builder|
29
+ builder.request :json
30
+ builder.use Faraday::Response::RaiseHttpError
31
+ builder.use FaradayMiddleware::Mashify
32
+ builder.use FaradayMiddleware::ParseJson
33
+ builder.adapter(adapter)
34
+ end
27
35
 
28
- request = Faraday.new
29
36
  response = request.put(uri, params, headers)
37
+
30
38
  response.body
31
39
  end
32
40
  end
@@ -6,7 +6,7 @@ module Lelylan
6
6
  module Connection
7
7
  private
8
8
 
9
- def connection(authenticate=true, raw=false, version=0, force_urlencoded=false, path='', method)
9
+ def connection(method='get', path='', authenticate=true, raw=false, version=0, force_urlencoded=false)
10
10
 
11
11
  options = {
12
12
  :headers => {'Accept' => 'application/json', 'User-Agent' => user_agent, 'Content-Type' => 'application/json'},
@@ -22,17 +22,18 @@ module Lelylan
22
22
 
23
23
  if path =~ /subscriptions/
24
24
  raise Lelylan::Error, 'To make a request to the realtime services you need both client id and client secret' if (!client_id or !client_secret) and path =~ /subscriptions/
25
- basic = Base64.encode64("#{self.client_id}:#{self.client_secret}")
26
- options[:headers].merge!('Authorization' => "Bearer #{basic}")
27
25
  end
28
26
 
29
- options[:headers].merge!('Content-Length' => '0') if method == :delete
27
+ if method == :delete
28
+ options[:headers].merge!('Content-Length' => '0')
29
+ end
30
30
 
31
31
  connection = Faraday.new(options) do |builder|
32
32
  builder.request :json
33
- builder.use Faraday::Response::RaiseHttpError
33
+ builder.use Faraday::Request::BasicAuthentication, self.client_id, self.client_secret if path =~ /subscriptions/
34
34
  builder.use FaradayMiddleware::Mashify
35
35
  builder.use FaradayMiddleware::ParseJson
36
+ builder.use Faraday::Response::RaiseHttpError
36
37
  builder.adapter(adapter)
37
38
  end
38
39
 
@@ -40,7 +40,7 @@ module Lelylan
40
40
  # raw - The Boolean value let return the complete response.
41
41
  # force_urlencoded - The Boolean value that force the url encoding.
42
42
  def request(method, path, options, authenticate, raw, version, force_urlencoded)
43
- response = connection(authenticate, raw, version, force_urlencoded, path).send(method) do |request|
43
+ response = connection(method, path, authenticate, raw, version, force_urlencoded).send(method) do |request|
44
44
  case method
45
45
  when :delete, :get
46
46
  request.url(path, options)
@@ -2,7 +2,7 @@ module Lelylan
2
2
  class Version
3
3
  MAJOR = 0 unless defined? MAJOR
4
4
  MINOR = 0 unless defined? MINOR
5
- PATCH = 3 unless defined? PATCH
5
+ PATCH = 4 unless defined? PATCH
6
6
  PRE = nil unless defined? PRE
7
7
 
8
8
  class << self
@@ -0,0 +1,9 @@
1
+ {
2
+ "status": 401,
3
+ "method": "POST",
4
+ "request": "http://api.lelylan.com/devices/4f4bb686d033a957c1000251",
5
+ "error": {
6
+ "code": "notifications.access.not_authorized",
7
+ "description": "Token not valid"
8
+ }
9
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "status": 404,
3
+ "method": "GET",
4
+ "request": "http://api.lelylan.com/devices/4f4ba959d033a95549000261",
5
+ "error": {
6
+ "code": "notifications.resource.not_found",
7
+ "description": "Resource not found",
8
+ "uri": "http://api.lelylan.com/devices/4f4ba959d033a95549000261"
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "status": 422,
3
+ "method": "POST",
4
+ "request": "http://api.lelylan.com/devices",
5
+ "error": {
6
+ "code": "notifications.resource.not_valid",
7
+ "description": "Name can't be blank.",
8
+ "body": { "name": "" }
9
+ }
10
+ }
@@ -9,7 +9,7 @@ describe Lelylan::Client::Type do
9
9
  describe '#physical_properties' do
10
10
 
11
11
  before do
12
- stub_request(:put, 'http://mqtt.lelylan.com/devices/1').to_return(status: 202)
12
+ stub_request(:put, 'http://mqtt.lelylan.com/devices/1').to_return(status: 202, body: fixture('device.json'))
13
13
  end
14
14
 
15
15
  let!(:device) do
@@ -17,7 +17,7 @@ describe Lelylan::Client::Type do
17
17
  end
18
18
 
19
19
  it 'returns the type' do
20
- device.should be_nil
20
+ device.id.should_not be_nil
21
21
  end
22
22
 
23
23
  it 'sends the request' do
@@ -13,7 +13,7 @@ describe Lelylan::Client::Subscription do
13
13
  describe '#subscription' do
14
14
 
15
15
  before do
16
- stub_get('/subscriptions/1').to_return(body: fixture('subscription.json'))
16
+ stub_request(:get, 'http://id:secret@api.lelylan.com/subscriptions/1').to_return(body: fixture('subscription.json'))
17
17
  end
18
18
 
19
19
  let!(:subscription) do
@@ -25,7 +25,7 @@ describe Lelylan::Client::Subscription do
25
25
  end
26
26
 
27
27
  it 'sends the request' do
28
- a_get('/subscriptions/1').with(headers: { Authorization: basic }).should have_been_made
28
+ a_request(:get, 'http://id:secret@api.lelylan.com/subscriptions/1').should have_been_made
29
29
  end
30
30
  end
31
31
 
@@ -33,7 +33,7 @@ describe Lelylan::Client::Subscription do
33
33
  describe '#subscriptions' do
34
34
 
35
35
  before do
36
- stub_get('/subscriptions').to_return(body: fixture('subscriptions.json'))
36
+ stub_request(:get, 'http://id:secret@api.lelylan.com/subscriptions').to_return(body: fixture('subscriptions.json'))
37
37
  end
38
38
 
39
39
  let!(:subscriptions) do
@@ -45,13 +45,13 @@ describe Lelylan::Client::Subscription do
45
45
  end
46
46
 
47
47
  it 'sends the request' do
48
- a_get('/subscriptions').with(headers: { Authorization: basic }).should have_been_made
48
+ a_request(:get, 'http://id:secret@api.lelylan.com/subscriptions').should have_been_made
49
49
  end
50
50
 
51
51
  context 'with params' do
52
52
 
53
53
  before do
54
- stub_get('/subscriptions').with(query: { per: '25' }).to_return(body: fixture('subscription.json'))
54
+ stub_request(:get, 'http://id:secret@api.lelylan.com/subscriptions').with(query: { per: '25' }).to_return(body: fixture('subscription.json'))
55
55
  end
56
56
 
57
57
  before do
@@ -59,7 +59,7 @@ describe Lelylan::Client::Subscription do
59
59
  end
60
60
 
61
61
  it 'sends the params' do
62
- a_get('/subscriptions').with(headers: { Authorization: basic }).with(query: { per: '25' }).should have_been_made
62
+ a_request(:get, 'http://id:secret@api.lelylan.com/subscriptions').with(query: { per: '25' }).should have_been_made
63
63
  end
64
64
  end
65
65
  end
@@ -68,11 +68,11 @@ describe Lelylan::Client::Subscription do
68
68
  describe '#create_subscription' do
69
69
 
70
70
  before do
71
- stub_post('/subscriptions').with(headers: { Authorization: basic }).with(body: { name: 'Bedroom' }).to_return(body: fixture('subscription.json'))
71
+ stub_request(:post, 'http://id:secret@api.lelylan.com/subscriptions').with(body: { event: 'property-update' }).to_return(body: fixture('subscription.json'))
72
72
  end
73
73
 
74
74
  let!(:subscription) do
75
- lelylan.create_subscription(name: 'Bedroom')
75
+ lelylan.create_subscription(event: 'property-update')
76
76
  end
77
77
 
78
78
  it 'returns the subscription' do
@@ -80,7 +80,7 @@ describe Lelylan::Client::Subscription do
80
80
  end
81
81
 
82
82
  it 'sends the request' do
83
- a_post('/subscriptions').with(headers: { Authorization: basic }).with(body: { name: 'Bedroom' }).should have_been_made
83
+ a_request(:post, 'http://id:secret@api.lelylan.com/subscriptions').with(body: { event: 'property-update' }).should have_been_made
84
84
  end
85
85
  end
86
86
 
@@ -88,11 +88,11 @@ describe Lelylan::Client::Subscription do
88
88
  describe '#update_subscription' do
89
89
 
90
90
  before do
91
- stub_put('/subscriptions/1').with(body: { name: 'Bedroom' }).to_return(body: fixture('subscription.json'))
91
+ stub_request(:put, 'http://id:secret@api.lelylan.com/subscriptions/1').with(body: { event: 'delete' }).to_return(body: fixture('subscription.json'))
92
92
  end
93
93
 
94
94
  let!(:subscription) do
95
- lelylan.update_subscription('1', name: 'Bedroom')
95
+ lelylan.update_subscription('1', event: 'delete')
96
96
  end
97
97
 
98
98
  it 'returns the subscription' do
@@ -100,7 +100,7 @@ describe Lelylan::Client::Subscription do
100
100
  end
101
101
 
102
102
  it 'sends the request' do
103
- a_put('/subscriptions/1').with(headers: { Authorization: basic }).with(body: { name: 'Bedroom' }).should have_been_made
103
+ a_request(:put, 'http://id:secret@api.lelylan.com/subscriptions/1').with(body: { event: 'delete' }).should have_been_made
104
104
  end
105
105
  end
106
106
 
@@ -108,7 +108,7 @@ describe Lelylan::Client::Subscription do
108
108
  describe '#delete_subscription' do
109
109
 
110
110
  before do
111
- stub_delete('/subscriptions/1').to_return(body: fixture('subscription.json'))
111
+ stub_request(:delete, 'http://id:secret@api.lelylan.com/subscriptions/1').to_return(body: fixture('subscription.json'))
112
112
  end
113
113
 
114
114
  let!(:subscription) do
@@ -120,10 +120,11 @@ describe Lelylan::Client::Subscription do
120
120
  end
121
121
 
122
122
  it 'sends the request' do
123
- a_delete('/subscriptions/1').with(headers: { Authorization: basic }).should have_been_made
123
+ a_request(:delete, 'http://id:secret@api.lelylan.com/subscriptions/1').should have_been_made
124
124
  end
125
125
  end
126
126
 
127
+
127
128
  describe 'when a client param misses' do
128
129
 
129
130
  let(:client) do
@@ -133,7 +134,7 @@ describe Lelylan::Client::Subscription do
133
134
  describe '#subscription' do
134
135
 
135
136
  before do
136
- stub_get('/subscriptions/1').to_return(body: fixture('subscription.json'))
137
+ stub_request(:get, 'http://id:secret@api.lelylan.com/subscriptions/1').to_return(body: fixture('subscription.json'))
137
138
  end
138
139
 
139
140
  it 'raises a Lelylan::Error' do