motion_ocean 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +25 -7
- data/lib/motion_ocean.rb +0 -1
- data/lib/motion_ocean/api.rb +5 -0
- data/lib/motion_ocean/api/authorization.rb +30 -0
- data/lib/motion_ocean/api/client.rb +115 -0
- data/lib/motion_ocean/client.rb +4 -6
- data/lib/motion_ocean/resource/base.rb +1 -7
- data/lib/motion_ocean/version.rb +1 -1
- data/spec/api/authorization_spec.rb +27 -0
- data/spec/api/client_spec.rb +53 -0
- data/spec/client_spec.rb +29 -4
- metadata +9 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e8d553b93122e2453a621d617a90831ec6e225d
|
4
|
+
data.tar.gz: ca435978b2a0e40383976aa343297413c49d80d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aabec4b730b1aa08865c0e844eda3521f0d72781814479ffebde1c0ce0334568bb2e722998489d5a32d63c488d10a6da6783d7d144d0b41d62e73702b8945123
|
7
|
+
data.tar.gz: de1257b71353f6f6a278d04a0b9ca68d779857166e133801fd81360878fa49c713d32a8a12baaaff77b695825fc1bbb5bddd68504cc36f437ae44ba90854d384
|
data/README.md
CHANGED
@@ -42,11 +42,13 @@ end
|
|
42
42
|
|
43
43
|
### Usage
|
44
44
|
|
45
|
-
|
45
|
+
The API client is based on NSURLSession and every request is executed asynchronously. Every MotionOcean method call therefore requires a block, which yields a `MotionOcean::API::Response`.
|
46
46
|
|
47
47
|
MotionOcean pretty much implements the DO API 1:1, so please check [their documentation](https://developers.digitalocean.com/v2/) for the available functions and options.
|
48
48
|
|
49
|
-
###
|
49
|
+
### The response object
|
50
|
+
|
51
|
+
#### success?
|
50
52
|
|
51
53
|
You can use `success?` to check if a successful HTTP status code was returned:
|
52
54
|
|
@@ -56,25 +58,41 @@ client.droplet.create(options) do |result|
|
|
56
58
|
end
|
57
59
|
```
|
58
60
|
|
59
|
-
|
61
|
+
#### data
|
62
|
+
|
63
|
+
The response data is assigned to the `data`-property of the response object. This is a `Hash` which
|
64
|
+
corresponds with the returned JSON by DigitalOcean.
|
60
65
|
|
61
|
-
|
66
|
+
``` ruby
|
67
|
+
client.droplet.create(options) do |result|
|
68
|
+
result.data # => Hash with the JSON response
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
|
73
|
+
#### response
|
74
|
+
|
75
|
+
MotionOcean uses NSURLSession. You can use `response` to get to the original response
|
62
76
|
object:
|
63
77
|
|
64
78
|
``` ruby
|
65
79
|
client.droplet.create(options) do |result|
|
66
|
-
result.response # =>
|
80
|
+
result.response # => NSHTTPURLResponse
|
67
81
|
end
|
68
82
|
```
|
69
|
-
|
83
|
+
#### error
|
84
|
+
|
85
|
+
When there is an error you can check this for the specific error. If you think it is a bug in MotionOcean, please open an issue or even better: submit a Pull Request with the fix!
|
70
86
|
|
71
87
|
## Aknowledgements
|
72
88
|
|
73
89
|
This library is an adaptation for RubyMotion of [barge](https://github.com/boats/barge) by [Ørjan Blom](https://github.com/blom).
|
74
90
|
|
91
|
+
The brilliant [MotionInMotion](https://motioninmotion.tv) screencasts helped me with implementing the API client using NSURLSession (specifically [episode #33](https://motioninmotion.tv/screencasts/33)) and iOS development using RubyMotion in general.
|
92
|
+
|
75
93
|
## Contributing
|
76
94
|
|
77
|
-
1. Fork it ( https://github.com/
|
95
|
+
1. Fork it ( https://github.com/datarift/motion_ocean/fork )
|
78
96
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
79
97
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
80
98
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/lib/motion_ocean.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
module MotionOcean
|
2
|
+
module API
|
3
|
+
class Authorization
|
4
|
+
class << self
|
5
|
+
def build(options = {})
|
6
|
+
username, password = options.values_at :username, :password
|
7
|
+
token = options.fetch(:token, nil)
|
8
|
+
token_name = options.fetch(:token_name, "Token")
|
9
|
+
|
10
|
+
if username && password
|
11
|
+
http_basic_header(username, password)
|
12
|
+
elsif token
|
13
|
+
token_header(token, token_name)
|
14
|
+
else
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def http_basic_header(username, password)
|
20
|
+
token_data = "#{username}:#{password}".dataUsingEncoding(NSUTF8StringEncoding)
|
21
|
+
token_header(token_data.base64Encoding, "Basic")
|
22
|
+
end
|
23
|
+
|
24
|
+
def token_header(token, token_name)
|
25
|
+
"#{token_name} #{token}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module MotionOcean
|
2
|
+
module API
|
3
|
+
class Client
|
4
|
+
attr_reader :base_url
|
5
|
+
|
6
|
+
def initialize(base_url, &block)
|
7
|
+
base_url += '/' unless base_url.end_with? '/'
|
8
|
+
@base_url = NSURL.URLWithString(base_url)
|
9
|
+
@headers = {}
|
10
|
+
|
11
|
+
# yield(self) if block_given?
|
12
|
+
if block_given?
|
13
|
+
case block.arity
|
14
|
+
when 0
|
15
|
+
instance_eval(&block)
|
16
|
+
when 1
|
17
|
+
block.call(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
[:delete, :get, :head, :post, :put].each do |verb|
|
23
|
+
define_method verb do |path, options = {}, &block|
|
24
|
+
request = create_request(path, options, verb)
|
25
|
+
create_task(request, &block).resume
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def header(name, value)
|
30
|
+
@headers[name] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def authorization(options = {})
|
34
|
+
header "Authorization", MotionOcean::API::Authorization.build(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def config
|
39
|
+
NSURLSessionConfiguration.defaultSessionConfiguration
|
40
|
+
end
|
41
|
+
|
42
|
+
def session
|
43
|
+
NSURLSession.sessionWithConfiguration(config)
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_request(path, options, method)
|
47
|
+
headers = options.fetch(:headers, {})
|
48
|
+
body = options.fetch(:body, nil)
|
49
|
+
query = options.fetch(:query, nil)
|
50
|
+
|
51
|
+
url = create_url(path, query)
|
52
|
+
|
53
|
+
request = NSMutableURLRequest.requestWithURL(url)
|
54
|
+
|
55
|
+
set_headers(request, @headers.merge(headers))
|
56
|
+
set_body(request, body) if body
|
57
|
+
|
58
|
+
request.setHTTPMethod(method.to_s.upcase)
|
59
|
+
|
60
|
+
request
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_url(path, query)
|
64
|
+
query_string = query.map { |key, value| "#{key}=#{value}" }.join '&' unless query.nil?
|
65
|
+
|
66
|
+
path.sub!('/', '') if path.start_with? '/'
|
67
|
+
|
68
|
+
components = NSURLComponents.new
|
69
|
+
components.path = path
|
70
|
+
components.query = query_string
|
71
|
+
components.URLRelativeToURL @base_url
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_headers(request, headers)
|
75
|
+
headers.each do |name, value|
|
76
|
+
request.addValue value.to_s, forHTTPHeaderField: name.to_s
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_body(request, params)
|
81
|
+
data = json_params(params)
|
82
|
+
request.setHTTPBody(data)
|
83
|
+
end
|
84
|
+
|
85
|
+
def json_params(params)
|
86
|
+
NSJSONSerialization.dataWithJSONObject(params, options: 0, error: nil)
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_task(request, &block)
|
90
|
+
if block_given?
|
91
|
+
session.dataTaskWithRequest(request, completionHandler: -> (data, response, error) {
|
92
|
+
block.call(APIResponse.new(data, response, error))
|
93
|
+
})
|
94
|
+
else
|
95
|
+
session.dataTaskWithRequest(request)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class APIResponse
|
100
|
+
attr_reader :success, :data, :error, :response
|
101
|
+
|
102
|
+
def initialize(data, response, error)
|
103
|
+
@success = (200...300).include?(response.statusCode) if response
|
104
|
+
@data = NSJSONSerialization.JSONObjectWithData(data, options: 0, error: nil)
|
105
|
+
@response = response
|
106
|
+
@error = error
|
107
|
+
end
|
108
|
+
|
109
|
+
def success?
|
110
|
+
@success
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/motion_ocean/client.rb
CHANGED
@@ -25,20 +25,18 @@ module MotionOcean
|
|
25
25
|
@action = Resource::Action.new(api_client)
|
26
26
|
@domain = Resource::Domain.new(api_client)
|
27
27
|
@droplet = Resource::Droplet.new(api_client)
|
28
|
+
@image = Resource::Image.new(api_client)
|
29
|
+
@key = Resource::Key.new(api_client)
|
28
30
|
@region = Resource::Region.new(api_client)
|
29
31
|
@size = Resource::Size.new(api_client)
|
30
32
|
end
|
31
33
|
|
32
34
|
def api_client
|
33
|
-
@api_client ||=
|
34
|
-
client.session_configuration :default
|
35
|
-
|
35
|
+
@api_client ||= MotionOcean::API::Client.new(DIGITAL_OCEAN_URL) do |client|
|
36
36
|
client.header 'Accept', 'application/json'
|
37
37
|
client.header 'Content-Type', 'application/json'
|
38
|
-
client.header 'Authorization', "Bearer #{access_token}"
|
39
38
|
|
40
|
-
client.
|
41
|
-
client.response_serializer :json
|
39
|
+
client.authorization token: access_token, token_name: 'Bearer'
|
42
40
|
end
|
43
41
|
end
|
44
42
|
end
|
@@ -21,13 +21,7 @@ module MotionOcean
|
|
21
21
|
opts = { per_page: PER_PAGE } if verb == :get
|
22
22
|
options = opts.merge(options)
|
23
23
|
|
24
|
-
api_client.public_send(verb, url, options)
|
25
|
-
data = response.object.tap do |r|
|
26
|
-
r.define_singleton_method(:response) { response }
|
27
|
-
r.define_singleton_method(:success?) { response.success? }
|
28
|
-
end
|
29
|
-
block.call(data)
|
30
|
-
end
|
24
|
+
api_client.public_send(verb, url, options, &block)
|
31
25
|
end
|
32
26
|
end
|
33
27
|
end
|
data/lib/motion_ocean/version.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
describe "MotionOcean::API::Authorization" do
|
2
|
+
before do
|
3
|
+
@token = 'some_token'
|
4
|
+
@token_name = 'FooBar'
|
5
|
+
@username = 'foo'
|
6
|
+
@password = 'bar'
|
7
|
+
@auth_data = 'Zm9vOmJhcg=='
|
8
|
+
end
|
9
|
+
|
10
|
+
it "creates a token-based auth header" do
|
11
|
+
header_value = MotionOcean::API::Authorization.build(token: @token)
|
12
|
+
|
13
|
+
header_value.should == "Token #{@token}"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "uses the specified token_name" do
|
17
|
+
header_value = MotionOcean::API::Authorization.build(token: @token, token_name: @token_name)
|
18
|
+
|
19
|
+
header_value.should == "#{@token_name} #{@token}"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "accepts HTTP Basic parameters" do
|
23
|
+
header_value = MotionOcean::API::Authorization.build(username: @username, password: @password)
|
24
|
+
|
25
|
+
header_value.should == "Basic #{@auth_data}"
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
describe "MotionOcean::API::Client" do
|
2
|
+
before do
|
3
|
+
@base_url = 'http://httpbin.org/'
|
4
|
+
@base_url_no_slash = 'http://httpbin.org'
|
5
|
+
@token = 'some_test_token'
|
6
|
+
end
|
7
|
+
|
8
|
+
it "accepts a base URL and a block" do
|
9
|
+
client = MotionOcean::API::Client.new(@base_url) do
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
client.base_url.should != nil
|
14
|
+
client.base_url.absoluteString.should == @base_url
|
15
|
+
end
|
16
|
+
|
17
|
+
it "adds a slash to the base_url if it is not present" do
|
18
|
+
client = MotionOcean::API::Client.new(@base_url_no_slash) do
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
client.base_url.should != nil
|
23
|
+
client.base_url.absoluteString.should == @base_url
|
24
|
+
end
|
25
|
+
|
26
|
+
it "accepts headers" do
|
27
|
+
client = MotionOcean::API::Client.new(@base_url_no_slash) do
|
28
|
+
header 'Accept', 'application/json'
|
29
|
+
end
|
30
|
+
|
31
|
+
headers = client.instance_variable_get(:@headers)
|
32
|
+
|
33
|
+
headers.should != nil
|
34
|
+
headers.empty?.should == false
|
35
|
+
headers.should.be.kind_of Hash
|
36
|
+
headers.include?('Accept').should == true
|
37
|
+
headers['Accept'].should == 'application/json'
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sets authorization headers" do
|
41
|
+
client = MotionOcean::API::Client.new(@base_url_no_slash) do
|
42
|
+
authorization token: 'some_test_token'
|
43
|
+
end
|
44
|
+
|
45
|
+
headers = client.instance_variable_get(:@headers)
|
46
|
+
|
47
|
+
headers.should != nil
|
48
|
+
headers.empty?.should == false
|
49
|
+
|
50
|
+
headers.include?('Authorization').should == true
|
51
|
+
headers['Authorization'].should == "Token #{@token}"
|
52
|
+
end
|
53
|
+
end
|
data/spec/client_spec.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
describe "MotionOcean::Client" do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
end
|
2
|
+
before do
|
3
|
+
@token ||= "some_token"
|
4
|
+
end
|
6
5
|
|
6
|
+
describe "class" do
|
7
|
+
it "is the right class" do
|
8
|
+
client = MotionOcean::Client.new(access_token: @token)
|
9
|
+
client.should.be.kind_of(MotionOcean::Client)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#access_token" do
|
7
14
|
it "can be set via a hash" do
|
8
15
|
client = MotionOcean::Client.new(access_token: @token)
|
9
16
|
expect(client.access_token).to eq(@token)
|
@@ -24,4 +31,22 @@ describe "MotionOcean::Client" do
|
|
24
31
|
end
|
25
32
|
|
26
33
|
end
|
34
|
+
|
35
|
+
describe "operation" do
|
36
|
+
it "can call the API" do
|
37
|
+
client = MotionOcean::Client.new(access_token: ENV['DO_API_TOKEN'])
|
38
|
+
client.should != nil
|
39
|
+
|
40
|
+
client.domain.all do |response|
|
41
|
+
expect(response).not_to be_nil
|
42
|
+
expect(response.success?).to be_true
|
43
|
+
expect(response.error).to be_nil
|
44
|
+
response.data.should.be.kind_of(Hash)
|
45
|
+
|
46
|
+
resume
|
47
|
+
end
|
48
|
+
|
49
|
+
wait_max(10) {}
|
50
|
+
end
|
51
|
+
end
|
27
52
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion_ocean
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erwin Boskma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dbt
|
@@ -30,26 +30,6 @@ dependencies:
|
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 1.1.0
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: afmotion
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '2.3'
|
40
|
-
- - ">="
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: 2.3.0
|
43
|
-
type: :runtime
|
44
|
-
prerelease: false
|
45
|
-
version_requirements: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - "~>"
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '2.3'
|
50
|
-
- - ">="
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: 2.3.0
|
53
33
|
- !ruby/object:Gem::Dependency
|
54
34
|
name: rake
|
55
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,6 +100,9 @@ extra_rdoc_files: []
|
|
120
100
|
files:
|
121
101
|
- README.md
|
122
102
|
- lib/motion_ocean.rb
|
103
|
+
- lib/motion_ocean/api.rb
|
104
|
+
- lib/motion_ocean/api/authorization.rb
|
105
|
+
- lib/motion_ocean/api/client.rb
|
123
106
|
- lib/motion_ocean/client.rb
|
124
107
|
- lib/motion_ocean/resource/action.rb
|
125
108
|
- lib/motion_ocean/resource/base.rb
|
@@ -130,6 +113,8 @@ files:
|
|
130
113
|
- lib/motion_ocean/resource/region.rb
|
131
114
|
- lib/motion_ocean/resource/size.rb
|
132
115
|
- lib/motion_ocean/version.rb
|
116
|
+
- spec/api/authorization_spec.rb
|
117
|
+
- spec/api/client_spec.rb
|
133
118
|
- spec/client_spec.rb
|
134
119
|
homepage: https://github.com/datarift/motion_ocean
|
135
120
|
licenses:
|
@@ -156,4 +141,6 @@ signing_key:
|
|
156
141
|
specification_version: 4
|
157
142
|
summary: Digital Ocean API implementation for RubyMotion
|
158
143
|
test_files:
|
144
|
+
- spec/api/authorization_spec.rb
|
145
|
+
- spec/api/client_spec.rb
|
159
146
|
- spec/client_spec.rb
|