nexmo 0.5.0 → 1.0.0

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 (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