nexmo 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README.md +28 -23
  2. data/lib/nexmo.rb +29 -43
  3. data/nexmo.gemspec +9 -3
  4. data/spec/nexmo_spec.rb +43 -55
  5. metadata +23 -7
data/README.md CHANGED
@@ -2,50 +2,55 @@ A simple wrapper for the [Nexmo](http://nexmo.com/) API
2
2
  =======================================================
3
3
 
4
4
 
5
- Requirements
6
- ------------
7
-
8
- Ruby 1.9; Ruby 1.8 is not currently supported.
9
-
10
-
11
5
  Installation
12
6
  ------------
13
7
 
14
- gem install nexmo
8
+ $ gem install nexmo
15
9
 
16
10
 
17
11
  Quick Start
18
12
  -----------
19
13
 
14
+ Use the `send_message!` for a "fire and forget" approach to sending a message:
15
+
20
16
  ```ruby
21
17
  require 'nexmo'
22
18
 
23
19
  nexmo = Nexmo::Client.new('...API KEY...', '...API SECRET...')
24
20
 
25
- response = nexmo.send_message({
26
- from: 'RUBY',
27
- to: '...NUMBER...',
28
- text: 'Hello world'
29
- })
30
-
31
- if response.success?
32
- puts "Sent message: #{response.message_id}"
33
- elsif response.failure?
34
- raise response.error
35
- end
21
+ nexmo.send_message!({:to => '...NUMBER...', :from => 'Ruby', :text => 'Hello world'})
22
+ ```
23
+
24
+ This method call returns the message id if the message was sent successfully,
25
+ or raises an exception if there was an error. For more robust error handling
26
+ use the `send_message` method instead. This returns the HTTP response wrapped
27
+ in a `Nexmo::Response` object.
28
+
29
+
30
+ JSON Implementation
31
+ -------------------
32
+
33
+ The "json" library is used by default. This is available in the Ruby 1.9
34
+ standard library, and as a gem for Ruby 1.8. You can specify an alternate
35
+ implementation that you wish to use explicitly when constructing a client
36
+ object. For example, to use [multi_json](https://rubygems.org/gems/multi_json):
37
+
38
+ ```ruby
39
+ require 'nexmo'
40
+ require 'multi_json'
41
+
42
+ nexmo = Nexmo::Client.new('...API KEY...', '...API SECRET...', :json => MultiJson)
36
43
  ```
37
44
 
45
+ Ditto for anything that is compatible with the default implementation.
46
+
38
47
 
39
48
  Troubleshooting
40
49
  ---------------
41
50
 
42
- Phone numbers should be specified in international format.
51
+ Remember that phone numbers should be specified in international format.
43
52
 
44
53
  The Nexmo documentation contains a [list of error codes](http://nexmo.com/documentation/index.html#response_code)
45
54
  which may be useful if you have problems sending a message.
46
55
 
47
-
48
- Bugs/Issues
49
- -----------
50
-
51
56
  Please report all bugs/issues via the GitHub issue tracker.
@@ -1,6 +1,6 @@
1
1
  require 'net/http'
2
+ require 'net/https' if RUBY_VERSION == '1.8.7'
2
3
  require 'json'
3
- require 'uri'
4
4
  require 'cgi'
5
5
 
6
6
  module Nexmo
@@ -10,34 +10,32 @@ module Nexmo
10
10
 
11
11
  @json = options.fetch(:json) { JSON }
12
12
 
13
- @headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
14
-
15
- @http = Net::HTTP.new('rest.nexmo.com', 443)
13
+ @http = Net::HTTP.new('rest.nexmo.com', Net::HTTP.https_default_port)
16
14
 
17
15
  @http.use_ssl = true
18
16
  end
19
17
 
20
- attr_accessor :key, :secret, :http, :headers
18
+ attr_accessor :key, :secret, :http
19
+
20
+ def send_message(params)
21
+ post('/sms/json', params)
22
+ end
21
23
 
22
- def send_message(data)
23
- response = @http.post('/sms/json', encode(data), headers)
24
+ def send_message!(params)
25
+ response = send_message(params)
24
26
 
25
- if response.code.to_i == 200 && response['Content-Type'].split(?;).first == 'application/json'
26
- object = @json.load(response.body)['messages'].first
27
+ if response.ok? && response.json?
28
+ item = response.object['messages'].first
27
29
 
28
- status = object['status'].to_i
30
+ status = item['status'].to_i
29
31
 
30
32
  if status == 0
31
- Success.new(object['message-id'])
33
+ item['message-id']
32
34
  else
33
- error = Error.new("#{object['error-text']} (status=#{status})")
34
-
35
- Failure.new(error, response, status)
35
+ raise Error, "#{item['error-text']} (status=#{status})"
36
36
  end
37
37
  else
38
- error = Error.new("Unexpected HTTP response (code=#{response.code})")
39
-
40
- Failure.new(error, response)
38
+ raise Error, "Unexpected HTTP response (code=#{response.code})"
41
39
  end
42
40
  end
43
41
 
@@ -70,17 +68,21 @@ module Nexmo
70
68
  end
71
69
 
72
70
  def search_messages(params)
73
- get("/search/messages/#{key}/#{secret}", Hash === params ? params : {ids: Array(params)})
71
+ get("/search/messages/#{key}/#{secret}", Hash === params ? params : {:ids => Array(params)})
74
72
  end
75
73
 
76
74
  private
77
75
 
78
76
  def get(path, params = {})
79
- Response.new(@http.get(request_uri(path, params)), json: @json)
77
+ Response.new(@http.get(request_uri(path, params)), :json => @json)
80
78
  end
81
79
 
82
- def encode(data)
83
- URI.encode_www_form data.merge(:username => @key, :password => @secret)
80
+ def post(path, params)
81
+ Response.new(@http.post(path, encode(params), {'Content-Type' => 'application/json'}), :json => @json)
82
+ end
83
+
84
+ def encode(params)
85
+ JSON.dump(params.merge(:username => @key, :password => @secret))
84
86
  end
85
87
 
86
88
  def request_uri(path, hash = {})
@@ -91,7 +93,7 @@ module Nexmo
91
93
  Array(values).map { |value| "#{escape(key)}=#{escape(value)}" }
92
94
  end
93
95
 
94
- path + '?' + query_params.flatten.join(?&)
96
+ path + '?' + query_params.flatten.join('&')
95
97
  end
96
98
  end
97
99
 
@@ -107,6 +109,10 @@ module Nexmo
107
109
  @json = options.fetch(:json) { JSON }
108
110
  end
109
111
 
112
+ def respond_to_missing?(name, include_private = false)
113
+ @http_response.respond_to?(name)
114
+ end
115
+
110
116
  def method_missing(name, *args, &block)
111
117
  @http_response.send(name, *args, &block)
112
118
  end
@@ -116,7 +122,7 @@ module Nexmo
116
122
  end
117
123
 
118
124
  def json?
119
- self['Content-Type'].split(?;).first == 'application/json'
125
+ self['Content-Type'].split(';').first == 'application/json'
120
126
  end
121
127
 
122
128
  def object
@@ -124,26 +130,6 @@ module Nexmo
124
130
  end
125
131
  end
126
132
 
127
- class Success < Struct.new(:message_id)
128
- def success?
129
- true
130
- end
131
-
132
- def failure?
133
- false
134
- end
135
- end
136
-
137
- class Failure < Struct.new(:error, :http, :status)
138
- def success?
139
- false
140
- end
141
-
142
- def failure?
143
- true
144
- end
145
- end
146
-
147
133
  class Error < StandardError
148
134
  end
149
135
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'nexmo'
3
- s.version = '0.5.0'
3
+ s.version = '1.0.0'
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.authors = ['Tim Craft']
6
6
  s.email = ['mail@timcraft.com']
@@ -8,7 +8,13 @@ Gem::Specification.new do |s|
8
8
  s.description = 'A simple wrapper for the Nexmo API'
9
9
  s.summary = 'See description'
10
10
  s.files = Dir.glob('{lib,spec}/**/*') + %w(README.md nexmo.gemspec)
11
- s.add_development_dependency('mocha', '~> 0.9.12')
12
- s.add_development_dependency('oj', '~> 1.3.7')
11
+ s.add_development_dependency('rake', '>= 0.9.3')
12
+ s.add_development_dependency('mocha', '~> 0.10.3')
13
+ s.add_development_dependency('multi_json', '~> 1.3.6')
13
14
  s.require_path = 'lib'
15
+
16
+ if RUBY_VERSION == '1.8.7'
17
+ s.add_development_dependency('minitest', '>= 4.2.0')
18
+ s.add_development_dependency('json', '>= 1.6.5')
19
+ end
14
20
  end
@@ -1,8 +1,7 @@
1
1
  require 'minitest/autorun'
2
2
  require 'mocha'
3
- require 'oj'
4
-
5
- require_relative '../lib/nexmo'
3
+ require 'multi_json'
4
+ require 'nexmo'
6
5
 
7
6
  describe 'Nexmo::Client' do
8
7
  before do
@@ -16,67 +15,55 @@ describe 'Nexmo::Client' do
16
15
  end
17
16
  end
18
17
 
19
- describe 'headers method' do
20
- it 'returns a hash' do
21
- @client.headers.must_be_kind_of(Hash)
22
- end
23
- end
24
-
25
18
  describe 'send_message method' do
26
- it 'posts to the sms resource' do
27
- http_response = stub(code: '200', body: '{"messages":[{"status":0,"message-id":"id"}]}')
28
- http_response.expects(:[]).with('Content-Type').returns('application/json;charset=utf-8')
19
+ it 'posts to the sms json resource and returns a response object' do
20
+ data = regexp_matches(/\{".+?":".+?"(,".+?":".+?")+\}/)
29
21
 
30
- data = 'from=ruby&to=number&text=Hey%21&username=key&password=secret'
22
+ headers = has_entry('Content-Type', 'application/json')
31
23
 
32
- headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
24
+ params = {:from => 'ruby', :to => 'number', :text => 'Hey!'}
33
25
 
34
- @client.http.expects(:post).with('/sms/json', data, headers).returns(http_response)
26
+ @client.http.expects(:post).with('/sms/json', data, headers).returns(stub)
35
27
 
36
- @client.send_message({from: 'ruby', to: 'number', text: 'Hey!'})
28
+ @client.send_message(params).must_be_instance_of(Nexmo::Response)
37
29
  end
30
+ end
38
31
 
39
- describe 'when the first message status equals 0' do
40
- it 'returns a success object' do
41
- http_response = stub(code: '200', body: '{"messages":[{"status":0,"message-id":"id"}]}')
42
- http_response.expects(:[]).with('Content-Type').returns('application/json;charset=utf-8')
43
-
44
- @client.http.stubs(:post).returns(http_response)
32
+ describe 'send_message bang method' do
33
+ before do
34
+ @params = {:from => 'ruby', :to => 'number', :text => 'Hey!'}
45
35
 
46
- response = @client.send_message({from: 'ruby', to: 'number', text: 'Hey!'})
47
- response.success?.must_equal(true)
48
- response.failure?.must_equal(false)
49
- response.message_id.must_equal('id')
50
- end
36
+ @http_response = mock()
37
+ @http_response.stubs(:[]).with('Content-Type').returns('application/json;charset=utf-8')
38
+ @http_response.stubs(:code).returns('200')
51
39
  end
52
40
 
53
- describe 'when the first message status does not equal 0' do
54
- it 'returns a failure object' do
55
- http_response = stub(code: '200', body: '{"messages":[{"status":2,"error-text":"Missing from param"}]}')
56
- http_response.expects(:[]).with('Content-Type').returns('application/json')
41
+ it 'posts to the sms json resource and returns the message id' do
42
+ data = regexp_matches(/\{".+?":".+?"(,".+?":".+?")+\}/)
43
+
44
+ headers = has_entry('Content-Type', 'application/json')
45
+
46
+ @http_response.stubs(:body).returns('{"messages":[{"status":0,"message-id":"id"}]}')
47
+
48
+ @client.http.expects(:post).with('/sms/json', data, headers).returns(@http_response)
49
+
50
+ @client.send_message!(@params).must_equal('id')
51
+ end
57
52
 
58
- @client.http.stubs(:post).returns(http_response)
53
+ it 'raises an exception if the response code is not expected' do
54
+ @http_response.stubs(:code).returns('500')
59
55
 
60
- response = @client.send_message({to: 'number', text: 'Hey!'})
56
+ @client.http.stubs(:post).returns(@http_response)
61
57
 
62
- response.success?.must_equal(false)
63
- response.failure?.must_equal(true)
64
- response.error.to_s.must_equal('Missing from param (status=2)')
65
- response.http.wont_be_nil
66
- end
58
+ proc { @client.send_message!(@params) }.must_raise(Nexmo::Error)
67
59
  end
68
60
 
69
- describe 'when the server returns an unexpected http response' do
70
- it 'returns a failure object' do
71
- @client.http.stubs(:post).returns(stub(code: '503'))
61
+ it 'raises an exception if the response body contains an error' do
62
+ @http_response.stubs(:body).returns('{"messages":[{"status":2,"error-text":"Missing from param"}]}')
72
63
 
73
- response = @client.send_message({from: 'ruby', to: 'number', text: 'Hey!'})
64
+ @client.http.stubs(:post).returns(@http_response)
74
65
 
75
- response.success?.must_equal(false)
76
- response.failure?.must_equal(true)
77
- response.error.to_s.must_equal('Unexpected HTTP response (code=503)')
78
- response.http.wont_be_nil
79
- end
66
+ proc { @client.send_message!(@params) }.must_raise(Nexmo::Error)
80
67
  end
81
68
  end
82
69
 
@@ -106,9 +93,9 @@ describe 'Nexmo::Client' do
106
93
 
107
94
  describe 'get_account_numbers method' do
108
95
  it 'fetches the account numbers resource with the given parameters and returns a response object' do
109
- @client.http.expects(:get).with('/account/numbers/key/secret?size=25&pattern=33').returns(stub)
96
+ @client.http.expects(:get).with(has_equivalent_query_string('/account/numbers/key/secret?size=25&pattern=33')).returns(stub)
110
97
 
111
- @client.get_account_numbers(size: 25, pattern: 33).must_be_instance_of(Nexmo::Response)
98
+ @client.get_account_numbers(:size => 25, :pattern => 33).must_be_instance_of(Nexmo::Response)
112
99
  end
113
100
  end
114
101
 
@@ -116,7 +103,7 @@ describe 'Nexmo::Client' do
116
103
  it 'fetches the number search resource for the given country with the given parameters and returns a response object' do
117
104
  @client.http.expects(:get).with('/number/search/key/secret/CA?size=25').returns(stub)
118
105
 
119
- @client.number_search(:CA, size: 25).must_be_instance_of(Nexmo::Response)
106
+ @client.number_search(:CA, :size => 25).must_be_instance_of(Nexmo::Response)
120
107
  end
121
108
  end
122
109
 
@@ -132,19 +119,19 @@ describe 'Nexmo::Client' do
132
119
  it 'fetches the message rejections resource with the given parameters and returns a response object' do
133
120
  @client.http.expects(:get).with('/search/rejections/key/secret?date=YYYY-MM-DD').returns(stub)
134
121
 
135
- @client.get_message_rejections(date: 'YYYY-MM-DD').must_be_instance_of(Nexmo::Response)
122
+ @client.get_message_rejections(:date => 'YYYY-MM-DD').must_be_instance_of(Nexmo::Response)
136
123
  end
137
124
  end
138
125
 
139
126
  describe 'search_messages method' do
140
127
  it 'fetches the search messages resource with the given parameters and returns a response object' do
141
- @client.http.expects(:get).with('/search/messages/key/secret?date=YYYY-MM-DD&to=1234567890').returns(stub)
128
+ @client.http.expects(:get).with(has_equivalent_query_string('/search/messages/key/secret?date=YYYY-MM-DD&to=1234567890')).returns(stub)
142
129
 
143
- @client.search_messages(date: 'YYYY-MM-DD', to: 1234567890).must_be_instance_of(Nexmo::Response)
130
+ @client.search_messages(:date => 'YYYY-MM-DD', :to => 1234567890).must_be_instance_of(Nexmo::Response)
144
131
  end
145
132
 
146
133
  it 'should encode a non hash argument as a list of ids' do
147
- @client.http.expects(:get).with('/search/messages/key/secret?ids=id1&ids=id2').returns(stub)
134
+ @client.http.expects(:get).with(has_equivalent_query_string('/search/messages/key/secret?ids=id1&ids=id2')).returns(stub)
148
135
 
149
136
  @client.search_messages(%w(id1 id2))
150
137
  end
@@ -161,6 +148,7 @@ describe 'Nexmo::Response' do
161
148
  it 'delegates to the underlying http response' do
162
149
  @http_response.expects(:code).returns('200')
163
150
 
151
+ @response.must_respond_to(:code) unless RUBY_VERSION == '1.8.7'
164
152
  @response.code.must_equal('200')
165
153
  end
166
154
 
@@ -205,7 +193,7 @@ describe 'Nexmo::Response initialized with a different json implementation' do
205
193
  before do
206
194
  @http_response = mock()
207
195
 
208
- @response = Nexmo::Response.new(@http_response, json: Oj)
196
+ @response = Nexmo::Response.new(@http_response, :json => MultiJson)
209
197
  end
210
198
 
211
199
  describe 'object method' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexmo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-08 00:00:00.000000000 Z
12
+ date: 2012-11-16 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.3
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.9.3
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: mocha
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -18,7 +34,7 @@ dependencies:
18
34
  requirements:
19
35
  - - ~>
20
36
  - !ruby/object:Gem::Version
21
- version: 0.9.12
37
+ version: 0.10.3
22
38
  type: :development
23
39
  prerelease: false
24
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,15 +42,15 @@ dependencies:
26
42
  requirements:
27
43
  - - ~>
28
44
  - !ruby/object:Gem::Version
29
- version: 0.9.12
45
+ version: 0.10.3
30
46
  - !ruby/object:Gem::Dependency
31
- name: oj
47
+ name: multi_json
32
48
  requirement: !ruby/object:Gem::Requirement
33
49
  none: false
34
50
  requirements:
35
51
  - - ~>
36
52
  - !ruby/object:Gem::Version
37
- version: 1.3.7
53
+ version: 1.3.6
38
54
  type: :development
39
55
  prerelease: false
40
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +58,7 @@ dependencies:
42
58
  requirements:
43
59
  - - ~>
44
60
  - !ruby/object:Gem::Version
45
- version: 1.3.7
61
+ version: 1.3.6
46
62
  description: A simple wrapper for the Nexmo API
47
63
  email:
48
64
  - mail@timcraft.com