lelylan-rb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +26 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +7 -0
- data/Guardfile +5 -0
- data/LICENSE.md +1 -0
- data/README.md +218 -0
- data/Rakefile +21 -0
- data/lelylan_rb.gemspec +36 -0
- data/lib/faraday/response/raise_http_error.rb +35 -0
- data/lib/lelylan.rb +26 -0
- data/lib/lelylan/authentication.rb +11 -0
- data/lib/lelylan/client.rb +47 -0
- data/lib/lelylan/client/categories.rb +112 -0
- data/lib/lelylan/client/consumptions.rb +93 -0
- data/lib/lelylan/client/devices.rb +211 -0
- data/lib/lelylan/client/functions.rb +118 -0
- data/lib/lelylan/client/histories.rb +42 -0
- data/lib/lelylan/client/locations.rb +92 -0
- data/lib/lelylan/client/properties.rb +115 -0
- data/lib/lelylan/client/statuses.rb +110 -0
- data/lib/lelylan/client/types.rb +109 -0
- data/lib/lelylan/configuration.rb +65 -0
- data/lib/lelylan/connection.rb +33 -0
- data/lib/lelylan/error.rb +34 -0
- data/lib/lelylan/request.rb +70 -0
- data/lib/lelylan/version.rb +15 -0
- data/spec/faraday/response_spec.rb +37 -0
- data/spec/fixtures/categories.json +7 -0
- data/spec/fixtures/category.json +7 -0
- data/spec/fixtures/consumption.json +11 -0
- data/spec/fixtures/consumptions.json +11 -0
- data/spec/fixtures/device.json +25 -0
- data/spec/fixtures/devices.json +25 -0
- data/spec/fixtures/function.json +15 -0
- data/spec/fixtures/functions.json +15 -0
- data/spec/fixtures/histories.json +16 -0
- data/spec/fixtures/history.json +16 -0
- data/spec/fixtures/location.json +55 -0
- data/spec/fixtures/locations.json +18 -0
- data/spec/fixtures/oauth2/refresh.json +6 -0
- data/spec/fixtures/oauth2/token.json +6 -0
- data/spec/fixtures/pending.json +12 -0
- data/spec/fixtures/properties.json +9 -0
- data/spec/fixtures/property.json +9 -0
- data/spec/fixtures/status.json +24 -0
- data/spec/fixtures/statuses.json +24 -0
- data/spec/fixtures/type.json +94 -0
- data/spec/fixtures/types.json +88 -0
- data/spec/helper.rb +71 -0
- data/spec/lelylan/client/categories_spec.rb +178 -0
- data/spec/lelylan/client/consumptions_spec.rb +150 -0
- data/spec/lelylan/client/devices_spec.rb +342 -0
- data/spec/lelylan/client/functions_spec.rb +184 -0
- data/spec/lelylan/client/histories_spec.rb +64 -0
- data/spec/lelylan/client/locations_spec.rb +155 -0
- data/spec/lelylan/client/properties_spec.rb +184 -0
- data/spec/lelylan/client/statuses_spec.rb +184 -0
- data/spec/lelylan/client/types_spec.rb +184 -0
- data/spec/lelylan/client_spec.rb +32 -0
- data/spec/lelylan/oauth2_spec.rb +54 -0
- data/spec/lelylan_spec.rb +32 -0
- metadata +351 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'lelylan/version'
|
3
|
+
|
4
|
+
module Lelylan
|
5
|
+
module Configuration
|
6
|
+
VALID_OPTIONS_KEYS = [
|
7
|
+
:adapter,
|
8
|
+
:api_version,
|
9
|
+
:api_endpoint,
|
10
|
+
:web_endpoint,
|
11
|
+
:endpoint,
|
12
|
+
:user,
|
13
|
+
:password,
|
14
|
+
:proxy,
|
15
|
+
:token,
|
16
|
+
:user_agent,
|
17
|
+
:auto_traversal,
|
18
|
+
:per_page].freeze
|
19
|
+
|
20
|
+
DEFAULT_ADAPTER = Faraday.default_adapter
|
21
|
+
DEFAULT_API_VERSION = 0
|
22
|
+
DEFAULT_API_ENDPOINT = 'http://api.lelylan.com/'
|
23
|
+
DEFAULT_WEB_ENDPOINT = 'http://lelylan.com/'
|
24
|
+
DEFAULT_USER_AGENT = "Lelylan Ruby Gem #{Lelylan::Version}".freeze
|
25
|
+
DEFAULT_AUTO_TRAVERSAL = false
|
26
|
+
|
27
|
+
attr_accessor(*VALID_OPTIONS_KEYS)
|
28
|
+
|
29
|
+
def self.extended(base)
|
30
|
+
base.reset
|
31
|
+
end
|
32
|
+
|
33
|
+
def configure
|
34
|
+
yield self
|
35
|
+
end
|
36
|
+
|
37
|
+
def options
|
38
|
+
VALID_OPTIONS_KEYS.inject({}){|o,k| o.merge!(k => send(k)) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def api_endpoint=(value)
|
42
|
+
@api_endpoint = File.join(value, "")
|
43
|
+
end
|
44
|
+
|
45
|
+
def web_endpoint=(value)
|
46
|
+
@web_endpoint = File.join(value, "")
|
47
|
+
end
|
48
|
+
|
49
|
+
alias :endpoint= :api_endpoint=
|
50
|
+
alias :endpoint :api_endpoint
|
51
|
+
|
52
|
+
def reset
|
53
|
+
self.adapter = DEFAULT_ADAPTER
|
54
|
+
self.api_version = DEFAULT_API_VERSION
|
55
|
+
self.api_endpoint = DEFAULT_API_ENDPOINT
|
56
|
+
self.web_endpoint = DEFAULT_WEB_ENDPOINT
|
57
|
+
self.user = nil
|
58
|
+
self.password = nil
|
59
|
+
self.proxy = nil
|
60
|
+
self.token = nil
|
61
|
+
self.user_agent = DEFAULT_USER_AGENT
|
62
|
+
self.auto_traversal = DEFAULT_AUTO_TRAVERSAL
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'faraday_middleware'
|
2
|
+
require 'faraday/response/raise_http_error'
|
3
|
+
|
4
|
+
module Lelylan
|
5
|
+
module Connection
|
6
|
+
private
|
7
|
+
|
8
|
+
def connection(authenticate=true, raw=false, version=0, force_urlencoded=false)
|
9
|
+
|
10
|
+
options = {
|
11
|
+
:headers => {'Accept' => 'application/json', 'User-Agent' => user_agent, 'Content-Type' => 'application/json'},
|
12
|
+
:proxy => proxy,
|
13
|
+
:ssl => { :verify => false },
|
14
|
+
:url => Lelylan.api_endpoint
|
15
|
+
}
|
16
|
+
|
17
|
+
if authenticated?
|
18
|
+
self.token = token.refresh! if token.expired?
|
19
|
+
options[:headers].merge!('Authorization' => "Bearer #{token.token}")
|
20
|
+
end
|
21
|
+
|
22
|
+
connection = Faraday.new(options) do |builder|
|
23
|
+
builder.request :json
|
24
|
+
builder.use Faraday::Response::RaiseHttpError
|
25
|
+
builder.use FaradayMiddleware::Mashify
|
26
|
+
builder.use FaradayMiddleware::ParseJson
|
27
|
+
builder.adapter(adapter)
|
28
|
+
end
|
29
|
+
|
30
|
+
connection
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Lelylan
|
2
|
+
# Internal: Custom error class for rescuing from all Lelylan errors
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
# Internal: Raised when Lelylan returns a 400 HTTP status code
|
6
|
+
class BadRequest < Error; end
|
7
|
+
|
8
|
+
# Internal: Raised when Lelylan returns a 401 HTTP status code
|
9
|
+
class Unauthorized < Error; end
|
10
|
+
|
11
|
+
# Internal: Raised when Lelylan returns a 403 HTTP status code
|
12
|
+
class Forbidden < Error; end
|
13
|
+
|
14
|
+
# Internal: Raised when Lelylan returns a 404 HTTP status code
|
15
|
+
class NotFound < Error; end
|
16
|
+
|
17
|
+
# Internal: Raised when Lelylan returns a 406 HTTP status code
|
18
|
+
class NotAcceptable < Error; end
|
19
|
+
|
20
|
+
# Internal: Raised when Lelylan returns a 422 HTTP status code
|
21
|
+
class UnprocessableEntity < Error; end
|
22
|
+
|
23
|
+
# Internal: Raised when Lelylan returns a 500 HTTP status code
|
24
|
+
class InternalServerError < Error; end
|
25
|
+
|
26
|
+
# Internal: Raised when Lelylan returns a 501 HTTP status code
|
27
|
+
class NotImplemented < Error; end
|
28
|
+
|
29
|
+
# Internal: Raised when Lelylan returns a 502 HTTP status code
|
30
|
+
class BadGateway < Error; end
|
31
|
+
|
32
|
+
# Internal: Raised when Lelylan returns a 503 HTTP status code
|
33
|
+
class ServiceUnavailable < Error; end
|
34
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Lelylan
|
4
|
+
module Request
|
5
|
+
|
6
|
+
# Internal: Perform an HTTP DELETE request.
|
7
|
+
def delete(path, options={}, authenticate=true, raw=false, version=api_version, force_urlencoded=false)
|
8
|
+
request(:delete, path, options, authenticate, raw, version, force_urlencoded)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Internal: Perform an HTTP GET request.
|
12
|
+
def get(path, options={}, authenticate=true, raw=false, version=api_version, force_urlencoded=false)
|
13
|
+
request(:get, path, options, authenticate, raw, version, force_urlencoded)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Internal: Perform an HTTP PATCH request.
|
17
|
+
def patch(path, options={}, authenticate=true, raw=false, version=api_version, force_urlencoded=false)
|
18
|
+
request(:patch, path, options, authenticate, raw, version, force_urlencoded)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: Perform an HTTP POST request.
|
22
|
+
def post(path, options={}, authenticate=true, raw=false, version=api_version, force_urlencoded=false)
|
23
|
+
request(:post, path, options, authenticate, raw, version, force_urlencoded)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Internal: Perform an HTTP PUT request.
|
27
|
+
def put(path, options={}, authenticate=true, raw=false, version=api_version, force_urlencoded=false)
|
28
|
+
request(:put, path, options, authenticate, raw, version, force_urlencoded)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Internal: Gets the user requests limit.
|
32
|
+
def ratelimit
|
33
|
+
headers = get("/ratelimit",{}, true, true).headers
|
34
|
+
return headers["X-RateLimit-Limit"].to_i
|
35
|
+
end
|
36
|
+
alias rate_limit ratelimit
|
37
|
+
|
38
|
+
# Internal: Gets the remaining user requests.
|
39
|
+
def ratelimit_remaining
|
40
|
+
headers = get("/ratelimit",{}, api_version, true, true).headers
|
41
|
+
return headers["X-RateLimit-Remaining"].to_i
|
42
|
+
end
|
43
|
+
alias rate_limit_remaining ratelimit_remaining
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Internal: Perform the HTTP request.
|
48
|
+
#
|
49
|
+
# method - The Symbol representing the HTTP method.
|
50
|
+
# path - The String URI to call.
|
51
|
+
# options - The Hash options containing the params to send to the
|
52
|
+
# service.
|
53
|
+
# authenticate - The Boolean value that authenticate the user.
|
54
|
+
# raw - The Boolean value let return the complete response.
|
55
|
+
# force_urlencoded - The Boolean value that force the url encoding.
|
56
|
+
def request(method, path, options, authenticate, raw, version, force_urlencoded)
|
57
|
+
response = connection(authenticate, raw, version, force_urlencoded).send(method) do |request|
|
58
|
+
case method
|
59
|
+
when :delete, :get
|
60
|
+
request.url(path, options)
|
61
|
+
when :patch, :post, :put
|
62
|
+
request.url(path)
|
63
|
+
request.body = options unless options.empty?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
raw ? response : response.body
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Lelylan
|
2
|
+
class Version
|
3
|
+
MAJOR = 0 unless defined? MAJOR
|
4
|
+
MINOR = 0 unless defined? MINOR
|
5
|
+
PATCH = 1 unless defined? PATCH
|
6
|
+
PRE = nil unless defined? PRE
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
[MAJOR, MINOR, PATCH, PRE].compact.join('.')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
describe Faraday::Response do
|
5
|
+
|
6
|
+
let(:client) do
|
7
|
+
Lelylan::Client.new
|
8
|
+
end
|
9
|
+
|
10
|
+
{
|
11
|
+
400 => Lelylan::BadRequest,
|
12
|
+
401 => Lelylan::Unauthorized,
|
13
|
+
403 => Lelylan::Forbidden,
|
14
|
+
404 => Lelylan::NotFound,
|
15
|
+
406 => Lelylan::NotAcceptable,
|
16
|
+
422 => Lelylan::UnprocessableEntity,
|
17
|
+
500 => Lelylan::InternalServerError,
|
18
|
+
501 => Lelylan::NotImplemented,
|
19
|
+
502 => Lelylan::BadGateway,
|
20
|
+
503 => Lelylan::ServiceUnavailable,
|
21
|
+
}.each do |status, exception|
|
22
|
+
|
23
|
+
context "when HTTP status is #{status}" do
|
24
|
+
|
25
|
+
before do
|
26
|
+
stub_get('http://api.lelylan.com/devices').
|
27
|
+
to_return(:status => status)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise #{exception.name} error" do
|
31
|
+
expect do
|
32
|
+
client.devices
|
33
|
+
end.to raise_error(exception)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{
|
2
|
+
"uri": "http://www.example.com/consumptions/5003c54dd033a96a3c0000fc",
|
3
|
+
"id": "5003c54dd033a96a3c0000fc",
|
4
|
+
"device": {
|
5
|
+
"uri": "http://www.example.com/devices/5003c54dd033a96a3c0000f8"
|
6
|
+
},
|
7
|
+
"type": "instantaneous",
|
8
|
+
"value": 125.0,
|
9
|
+
"unit": "kwh",
|
10
|
+
"occur_at": "2012-07-09T09:39:57+02:00"
|
11
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
[{
|
2
|
+
"uri": "http://www.example.com/consumptions/5003c54ad033a96a3c00000b",
|
3
|
+
"id": "5003c54ad033a96a3c00000b",
|
4
|
+
"device": {
|
5
|
+
"uri": "http://www.example.com/devices/5003c54ad033a96a3c000007"
|
6
|
+
},
|
7
|
+
"type": "instantaneous",
|
8
|
+
"value": 125.0,
|
9
|
+
"unit": "kwh",
|
10
|
+
"occur_at": "2012-07-09T09:39:54+02:00"
|
11
|
+
}]
|
@@ -0,0 +1,25 @@
|
|
1
|
+
{
|
2
|
+
"uri": "http://www.example.com/devices/5003c611d033a96b96000271",
|
3
|
+
"id": "5003c611d033a96b96000271",
|
4
|
+
"name": "Closet dimmer",
|
5
|
+
"type": {
|
6
|
+
"uri": "https://type.lelylan.com/types/dimmer"
|
7
|
+
},
|
8
|
+
"properties": [{
|
9
|
+
"uri": "https://type.lelylan.com/properties/status",
|
10
|
+
"value": "off"
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"uri": "https://type.lelylan.com/properties/intensity",
|
14
|
+
"value": "0.0"
|
15
|
+
}],
|
16
|
+
"physical": {
|
17
|
+
"uri": "https://node.lelylan.com/devices/physical-dimmer"
|
18
|
+
},
|
19
|
+
"pending": {
|
20
|
+
"uri": "http://www.example.com/devices/5003c611d033a96b96000271/pending",
|
21
|
+
"status": "false"
|
22
|
+
},
|
23
|
+
"created_at": "2012-07-16T09:43:13+02:00",
|
24
|
+
"updated_at": "2012-07-16T09:43:13+02:00"
|
25
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
[{
|
2
|
+
"uri": "http://www.example.com/devices/5003c60ed033a96b96000009",
|
3
|
+
"id": "5003c60ed033a96b96000009",
|
4
|
+
"name": "Closet dimmer",
|
5
|
+
"type": {
|
6
|
+
"uri": "https://type.lelylan.com/types/dimmer"
|
7
|
+
},
|
8
|
+
"properties": [{
|
9
|
+
"uri": "https://type.lelylan.com/properties/status",
|
10
|
+
"value": "off"
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"uri": "https://type.lelylan.com/properties/intensity",
|
14
|
+
"value": "0.0"
|
15
|
+
}],
|
16
|
+
"physical": {
|
17
|
+
"uri": "https://node.lelylan.com/devices/physical-dimmer"
|
18
|
+
},
|
19
|
+
"pending": {
|
20
|
+
"uri": "http://www.example.com/devices/5003c60ed033a96b96000009/pending",
|
21
|
+
"status": false
|
22
|
+
},
|
23
|
+
"created_at": "2012-07-16T09:43:10+02:00",
|
24
|
+
"updated_at": "2012-07-16T09:43:10+02:00"
|
25
|
+
}]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"uri": "http://www.example.com/functions/500425ffd033a9b4ac0002e5",
|
3
|
+
"id": "500425ffd033a9b4ac0002e5",
|
4
|
+
"name": "Set intensity",
|
5
|
+
"created_at": "2012-07-16T14:32:31Z",
|
6
|
+
"updated_at": "2012-07-16T14:32:31Z",
|
7
|
+
"properties": [{
|
8
|
+
"uri": "http://www.example.com/properties/status",
|
9
|
+
"value": "on"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"uri": "http://www.example.com/properties/intensity",
|
13
|
+
"value": "0"
|
14
|
+
}]
|
15
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
[{
|
2
|
+
"uri": "http://www.example.com/functions/500425fbd033a9b4ac0000db",
|
3
|
+
"id": "500425fbd033a9b4ac0000db",
|
4
|
+
"name": "Set intensity",
|
5
|
+
"created_at": "2012-07-16T14:32:27Z",
|
6
|
+
"updated_at": "2012-07-16T14:32:27Z",
|
7
|
+
"properties": [{
|
8
|
+
"uri": "http://www.example.com/properties/status",
|
9
|
+
"value": "on"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"uri": "http://www.example.com/properties/intensity",
|
13
|
+
"value": "0"
|
14
|
+
}]
|
15
|
+
}]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
[{
|
2
|
+
"uri": "http://www.example.com/histories/5003c54fd033a96a3c0001a7",
|
3
|
+
"id": "5003c54fd033a96a3c0001a7",
|
4
|
+
"device": {
|
5
|
+
"uri": "http://www.example.com/devices/5003c54fd033a96a3c0001a3"
|
6
|
+
},
|
7
|
+
"properties": [{
|
8
|
+
"uri": "https://type.lelylan.com/properties/intensity",
|
9
|
+
"value": "100.0"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"uri": "https://type.lelylan.com/properties/status",
|
13
|
+
"value": "on"
|
14
|
+
}],
|
15
|
+
"created_at": "2012-07-09T09:39:59+02:00"
|
16
|
+
}]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
{
|
2
|
+
"uri": "http://www.example.com/histories/5003c552d033a96a3c000398",
|
3
|
+
"id": "5003c552d033a96a3c000398",
|
4
|
+
"device": {
|
5
|
+
"uri": "http://www.example.com/devices/5003c552d033a96a3c000394"
|
6
|
+
},
|
7
|
+
"properties": [{
|
8
|
+
"uri": "https://type.lelylan.com/properties/intensity",
|
9
|
+
"value": "100.0"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"uri": "https://type.lelylan.com/properties/status",
|
13
|
+
"value": "on"
|
14
|
+
}],
|
15
|
+
"created_at": "2012-07-09T09:40:02+02:00"
|
16
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
{
|
2
|
+
"uri": "http://www.example.com/locations/1",
|
3
|
+
"id": "1",
|
4
|
+
"name": "Floor",
|
5
|
+
"type": "floor",
|
6
|
+
"created_at": "2012-07-16T17:04:14Z",
|
7
|
+
"updated_at": "2012-07-16T17:04:14Z",
|
8
|
+
"locations": {
|
9
|
+
"parent": {
|
10
|
+
"uri": "http://www.example.com/locations/2"
|
11
|
+
},
|
12
|
+
"children": [{
|
13
|
+
"uri": "http://www.example.com/locations/4"
|
14
|
+
}],
|
15
|
+
"ancestors": [{
|
16
|
+
"uri": "http://www.example.com/locations/3"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"uri": "http://www.example.com/locations/2"
|
20
|
+
}],
|
21
|
+
"descendants": [{
|
22
|
+
"uri": "http://www.example.com/locations/4"
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"uri": "http://www.example.com/locations/5"
|
26
|
+
}]
|
27
|
+
},
|
28
|
+
"devices": {
|
29
|
+
"children": [{
|
30
|
+
"uri": {
|
31
|
+
"uri": "https://device.lelylan.com/devices/closet-dimmer"
|
32
|
+
}
|
33
|
+
},
|
34
|
+
{
|
35
|
+
"uri": {
|
36
|
+
"uri": "https://device.lelylan.com/devices/another-closet-dimmer"
|
37
|
+
}
|
38
|
+
}],
|
39
|
+
"descendants": [{
|
40
|
+
"uri": {
|
41
|
+
"uri": "https://device.lelylan.com/devices/closet-dimmer"
|
42
|
+
}
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"uri": {
|
46
|
+
"uri": "https://device.lelylan.com/devices/another-closet-dimmer"
|
47
|
+
}
|
48
|
+
},
|
49
|
+
{
|
50
|
+
"uri": {
|
51
|
+
"uri": "https://device.lelylan.com/devices/descendant-closet-dimmer"
|
52
|
+
}
|
53
|
+
}]
|
54
|
+
}
|
55
|
+
}
|