lelylan-rb 0.0.1

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.
Files changed (62) hide show
  1. data/.gitignore +26 -0
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +7 -0
  4. data/Guardfile +5 -0
  5. data/LICENSE.md +1 -0
  6. data/README.md +218 -0
  7. data/Rakefile +21 -0
  8. data/lelylan_rb.gemspec +36 -0
  9. data/lib/faraday/response/raise_http_error.rb +35 -0
  10. data/lib/lelylan.rb +26 -0
  11. data/lib/lelylan/authentication.rb +11 -0
  12. data/lib/lelylan/client.rb +47 -0
  13. data/lib/lelylan/client/categories.rb +112 -0
  14. data/lib/lelylan/client/consumptions.rb +93 -0
  15. data/lib/lelylan/client/devices.rb +211 -0
  16. data/lib/lelylan/client/functions.rb +118 -0
  17. data/lib/lelylan/client/histories.rb +42 -0
  18. data/lib/lelylan/client/locations.rb +92 -0
  19. data/lib/lelylan/client/properties.rb +115 -0
  20. data/lib/lelylan/client/statuses.rb +110 -0
  21. data/lib/lelylan/client/types.rb +109 -0
  22. data/lib/lelylan/configuration.rb +65 -0
  23. data/lib/lelylan/connection.rb +33 -0
  24. data/lib/lelylan/error.rb +34 -0
  25. data/lib/lelylan/request.rb +70 -0
  26. data/lib/lelylan/version.rb +15 -0
  27. data/spec/faraday/response_spec.rb +37 -0
  28. data/spec/fixtures/categories.json +7 -0
  29. data/spec/fixtures/category.json +7 -0
  30. data/spec/fixtures/consumption.json +11 -0
  31. data/spec/fixtures/consumptions.json +11 -0
  32. data/spec/fixtures/device.json +25 -0
  33. data/spec/fixtures/devices.json +25 -0
  34. data/spec/fixtures/function.json +15 -0
  35. data/spec/fixtures/functions.json +15 -0
  36. data/spec/fixtures/histories.json +16 -0
  37. data/spec/fixtures/history.json +16 -0
  38. data/spec/fixtures/location.json +55 -0
  39. data/spec/fixtures/locations.json +18 -0
  40. data/spec/fixtures/oauth2/refresh.json +6 -0
  41. data/spec/fixtures/oauth2/token.json +6 -0
  42. data/spec/fixtures/pending.json +12 -0
  43. data/spec/fixtures/properties.json +9 -0
  44. data/spec/fixtures/property.json +9 -0
  45. data/spec/fixtures/status.json +24 -0
  46. data/spec/fixtures/statuses.json +24 -0
  47. data/spec/fixtures/type.json +94 -0
  48. data/spec/fixtures/types.json +88 -0
  49. data/spec/helper.rb +71 -0
  50. data/spec/lelylan/client/categories_spec.rb +178 -0
  51. data/spec/lelylan/client/consumptions_spec.rb +150 -0
  52. data/spec/lelylan/client/devices_spec.rb +342 -0
  53. data/spec/lelylan/client/functions_spec.rb +184 -0
  54. data/spec/lelylan/client/histories_spec.rb +64 -0
  55. data/spec/lelylan/client/locations_spec.rb +155 -0
  56. data/spec/lelylan/client/properties_spec.rb +184 -0
  57. data/spec/lelylan/client/statuses_spec.rb +184 -0
  58. data/spec/lelylan/client/types_spec.rb +184 -0
  59. data/spec/lelylan/client_spec.rb +32 -0
  60. data/spec/lelylan/oauth2_spec.rb +54 -0
  61. data/spec/lelylan_spec.rb +32 -0
  62. 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,7 @@
1
+ [{
2
+ "uri": "http://www.example.com/categories/500425f5d033a9b4ac000003",
3
+ "id": "500425f5d033a9b4ac000003",
4
+ "name": "Lighting",
5
+ "created_at": "2012-07-16T14:32:21Z",
6
+ "updated_at": "2012-07-16T14:32:21Z"
7
+ }]
@@ -0,0 +1,7 @@
1
+ {
2
+ "uri": "http://www.example.com/categories/500425f8d033a9b4ac0000b1",
3
+ "id": "500425f8d033a9b4ac0000b1",
4
+ "name": "Lighting",
5
+ "created_at": "2012-07-16T14:32:24Z",
6
+ "updated_at": "2012-07-16T14:32:24Z"
7
+ }
@@ -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
+ }