sendgrid-api 0.0.1 → 0.0.2
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.
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +6 -3
- data/Gemfile.lock +35 -1
- data/README.md +48 -4
- data/Rakefile +3 -1
- data/lib/sendgrid/api.rb +3 -2
- data/lib/sendgrid/api/client.rb +27 -0
- data/lib/sendgrid/api/entities/entity.rb +83 -0
- data/lib/sendgrid/api/entities/profile.rb +14 -0
- data/lib/sendgrid/api/entities/response.rb +21 -0
- data/lib/sendgrid/api/entities/stats.rb +14 -0
- data/lib/sendgrid/api/rest/errors/error.rb +60 -0
- data/lib/sendgrid/api/rest/resource.rb +56 -0
- data/lib/sendgrid/api/rest/response/parse_error.rb +19 -0
- data/lib/sendgrid/api/rest/response/parse_json.rb +18 -0
- data/lib/sendgrid/api/service.rb +23 -0
- data/lib/sendgrid/api/version.rb +2 -2
- data/lib/sendgrid/api/web/profile.rb +38 -0
- data/lib/sendgrid/api/web/stats.rb +36 -0
- data/sendgrid-api.gemspec +4 -1
- data/spec/fixtures/forbidden.json +3 -0
- data/spec/fixtures/profile.json +18 -0
- data/spec/fixtures/stats.json +50 -0
- data/spec/fixtures/success.json +3 -0
- data/spec/fixtures/unauthorized.json +6 -0
- data/spec/sendgrid/api/client_spec.rb +22 -0
- data/spec/sendgrid/api/entities/entity_spec.rb +279 -0
- data/spec/sendgrid/api/entities/profile_spec.rb +26 -0
- data/spec/sendgrid/api/entities/response_spec.rb +28 -0
- data/spec/sendgrid/api/entities/stats_spec.rb +25 -0
- data/spec/sendgrid/api/rest/errors/error_spec.rb +97 -0
- data/spec/sendgrid/api/rest/resource_spec.rb +143 -0
- data/spec/sendgrid/api/rest/response/parse_error_spec.rb +39 -0
- data/spec/sendgrid/api/rest/response/parse_json_spec.rb +45 -0
- data/spec/sendgrid/api/service_spec.rb +44 -0
- data/spec/sendgrid/api/version_spec.rb +2 -2
- data/spec/sendgrid/api/web/profile_spec.rb +126 -0
- data/spec/sendgrid/api/web/stats_spec.rb +100 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/helpers.rb +15 -0
- data/spec/support/mock.rb +26 -0
- data/spec/support/shared_examples.rb +11 -0
- metadata +78 -9
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'sendgrid/api/rest/errors/error'
|
3
|
+
|
4
|
+
module Sendgrid
|
5
|
+
module API
|
6
|
+
module REST
|
7
|
+
module Response
|
8
|
+
class ParseError < Faraday::Response::Middleware
|
9
|
+
|
10
|
+
def on_complete(env)
|
11
|
+
error = REST::Errors::Error.from_response(env)
|
12
|
+
raise error unless error.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Sendgrid
|
5
|
+
module API
|
6
|
+
module REST
|
7
|
+
module Response
|
8
|
+
class ParseJson < Faraday::Response::Middleware
|
9
|
+
|
10
|
+
def parse(body)
|
11
|
+
JSON.parse(body, :symbolize_names => true) if body
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sendgrid
|
2
|
+
module API
|
3
|
+
class Service
|
4
|
+
|
5
|
+
attr_reader :resource
|
6
|
+
|
7
|
+
def initialize(resource)
|
8
|
+
@resource = resource
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform_request(entity, url, params = {})
|
12
|
+
entity.from_response(request(url, params))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def request(url, params = {})
|
18
|
+
resource.post(url, params)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/sendgrid/api/version.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'sendgrid/api/service'
|
2
|
+
require 'sendgrid/api/entities/profile'
|
3
|
+
require 'sendgrid/api/entities/response'
|
4
|
+
|
5
|
+
module Sendgrid
|
6
|
+
module API
|
7
|
+
module Web
|
8
|
+
module Profile
|
9
|
+
|
10
|
+
def profile
|
11
|
+
Services.new(resource)
|
12
|
+
end
|
13
|
+
|
14
|
+
class Services < Sendgrid::API::Service
|
15
|
+
|
16
|
+
# View your SendGrid profile
|
17
|
+
#
|
18
|
+
# @see http://sendgrid.com/docs/API_Reference/Web_API/profile.html
|
19
|
+
# @return profile [Profile] An Entities::Profile object.
|
20
|
+
def get
|
21
|
+
perform_request(Entities::Profile, 'profile.get.json').first
|
22
|
+
end
|
23
|
+
|
24
|
+
# Update your SendGrid profile
|
25
|
+
#
|
26
|
+
# @see http://sendgrid.com/docs/API_Reference/Web_API/profile.html#-set
|
27
|
+
# @param profile [Profile] An Entities::Profile object.
|
28
|
+
# @return response [Response] An Entities::Response object.
|
29
|
+
def set(profile)
|
30
|
+
perform_request(Entities::Response, 'profile.set.json', profile.as_json)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'sendgrid/api/service'
|
2
|
+
require 'sendgrid/api/entities/stats'
|
3
|
+
|
4
|
+
module Sendgrid
|
5
|
+
module API
|
6
|
+
module Web
|
7
|
+
module Stats
|
8
|
+
|
9
|
+
def stats
|
10
|
+
Services.new(resource)
|
11
|
+
end
|
12
|
+
|
13
|
+
class Services < Sendgrid::API::Service
|
14
|
+
|
15
|
+
# Get Advanced Statistics
|
16
|
+
#
|
17
|
+
# @see http://sendgrid.com/docs/API_Reference/Web_API/Statistics/statistics_advanced.html
|
18
|
+
# @param options [Hash] A customizable set of options.
|
19
|
+
# @option options [String] :data_type One of the following: browsers, clients, devices, geo, global, isps. Required.
|
20
|
+
# @option options [Date] :start_date Date format is based on aggregated_by value (default is yyyy-mm-dd). Required.
|
21
|
+
# @option options [Date] :end_date Date format is based on aggregated_by value (default is yyyy-mm-dd).
|
22
|
+
# @option options [String] :metric One of the following (default is all): open, click, unique_open, unique_click, processed, delivered, drop, bounce, deferred, spamreport, blocked, all.
|
23
|
+
# @option options [String] :category Return stats for the given category.
|
24
|
+
# @option options [String] :aggregated_by Aggregate the data by the given period (default is day): day, week or month.
|
25
|
+
# @option options [String] :country Get stats for each region/state for the given country. Only US (United States) and CA (Canada) is supported at this time.
|
26
|
+
# @return stats [Stats] An array of Entities::Stats object.
|
27
|
+
def advanced(params = {})
|
28
|
+
perform_request(Entities::Stats, 'stats.getAdvanced.json', params)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/sendgrid-api.gemspec
CHANGED
@@ -5,7 +5,7 @@ require 'sendgrid/api/version'
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "sendgrid-api"
|
8
|
-
spec.version = Sendgrid::
|
8
|
+
spec.version = Sendgrid::API::VERSION
|
9
9
|
spec.authors = ["Renato Neves"]
|
10
10
|
spec.email = ["renatosnrg@gmail.com"]
|
11
11
|
spec.description = %q{A Ruby interface to the SendGrid API}
|
@@ -17,4 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'faraday', '~> 0.8.8'
|
22
|
+
spec.add_dependency 'json', '~> 1.8.0'
|
20
23
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"username":"sendgrid",
|
4
|
+
"email":"contact@sendgrid.com",
|
5
|
+
"active":"true",
|
6
|
+
"first_name":"Jim",
|
7
|
+
"last_name":"Franklin",
|
8
|
+
"address":"1065 N Pacificenter Drive, Suite 425",
|
9
|
+
"address2":"",
|
10
|
+
"city":"Anaheim",
|
11
|
+
"state":"CA",
|
12
|
+
"zip":"92806",
|
13
|
+
"country":"US",
|
14
|
+
"phone":"123456789",
|
15
|
+
"website":"http:\/\/www.sendgrid.com",
|
16
|
+
"website_access":"true"
|
17
|
+
}
|
18
|
+
]
|
@@ -0,0 +1,50 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"delivered":4792,
|
4
|
+
"unique_open":308,
|
5
|
+
"spamreport":3,
|
6
|
+
"unique_click":11,
|
7
|
+
"drop":57,
|
8
|
+
"request":5359,
|
9
|
+
"bounce":622,
|
10
|
+
"deferred":1975,
|
11
|
+
"processed":5302,
|
12
|
+
"date":"2013-06-18",
|
13
|
+
"open":481,
|
14
|
+
"click":11,
|
15
|
+
"blocked":29
|
16
|
+
},
|
17
|
+
{
|
18
|
+
"delivered":3,
|
19
|
+
"unique_open":17,
|
20
|
+
"unique_click":1,
|
21
|
+
"deferred":405,
|
22
|
+
"date":"2013-06-19",
|
23
|
+
"open":37,
|
24
|
+
"click":1
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"date":"2013-06-20",
|
28
|
+
"deferred":381,
|
29
|
+
"unique_open":4,
|
30
|
+
"open":7,
|
31
|
+
"bounce":2
|
32
|
+
},
|
33
|
+
{
|
34
|
+
"unique_open":2,
|
35
|
+
"bounce":1,
|
36
|
+
"deferred":113,
|
37
|
+
"date":"2013-06-21",
|
38
|
+
"open":8,
|
39
|
+
"blocked":31
|
40
|
+
},
|
41
|
+
{
|
42
|
+
"date":"2013-06-23",
|
43
|
+
"unique_open":2,
|
44
|
+
"open":2
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"date":"2013-06-24",
|
48
|
+
"open":1
|
49
|
+
}
|
50
|
+
]
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Sendgrid
|
4
|
+
module API
|
5
|
+
describe Client do
|
6
|
+
|
7
|
+
subject { described_class.new(user, key) }
|
8
|
+
let(:user) { 'some user' }
|
9
|
+
let(:key) { 'some key' }
|
10
|
+
|
11
|
+
its(:user) { should == user }
|
12
|
+
its(:key) { should == key }
|
13
|
+
|
14
|
+
it { should respond_to(:profile) }
|
15
|
+
it { should respond_to(:stats) }
|
16
|
+
|
17
|
+
its(:profile) { should_not be_nil }
|
18
|
+
its(:stats) { should_not be_nil }
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Sendgrid
|
4
|
+
module API
|
5
|
+
module Entities
|
6
|
+
describe Entity do
|
7
|
+
|
8
|
+
subject { described_class }
|
9
|
+
|
10
|
+
after do
|
11
|
+
described_class.clear_attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '.clear_attributes' do
|
15
|
+
before do
|
16
|
+
subject.attribute :attr1
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should clear the attributes' do
|
20
|
+
subject.clear_attributes
|
21
|
+
subject.attributes.should be_empty
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.attribute' do
|
26
|
+
context 'when the attributes are empty' do
|
27
|
+
before do
|
28
|
+
subject.clear_attributes
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should set the entity attributes' do
|
32
|
+
subject.attribute :attr1, :attr2
|
33
|
+
subject.attributes.should have(2).items
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when the attributes are not empty' do
|
38
|
+
before do
|
39
|
+
subject.clear_attributes
|
40
|
+
subject.attribute :attr1
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should add attributes to the entity' do
|
44
|
+
subject.attribute :attr2
|
45
|
+
subject.attributes.should have(2).items
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when the attributes are repeated' do
|
50
|
+
before do
|
51
|
+
subject.clear_attributes
|
52
|
+
subject.attribute :attr1
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should add distinct attributes to the entity' do
|
56
|
+
subject.attribute :attr1, :attr2
|
57
|
+
subject.attributes.should have(2).items
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#attributes' do
|
63
|
+
before do
|
64
|
+
subject.clear_attributes
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'without attributes' do
|
68
|
+
its(:attributes) { should be_empty }
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with attributes' do
|
72
|
+
before do
|
73
|
+
subject.attribute :attr1, :attr2
|
74
|
+
end
|
75
|
+
|
76
|
+
its(:attributes) { should have(2).items }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '.from_response' do
|
81
|
+
context 'when response is an array' do
|
82
|
+
before do
|
83
|
+
response.should_receive(:body).and_return([item1, item2])
|
84
|
+
end
|
85
|
+
let(:response) { double('response') }
|
86
|
+
let(:item1) { double('item').as_null_object }
|
87
|
+
let(:item2) { double('item').as_null_object }
|
88
|
+
|
89
|
+
subject { described_class.from_response(response) }
|
90
|
+
|
91
|
+
it { should have(2).items }
|
92
|
+
its([0]) { should be_instance_of(described_class) }
|
93
|
+
its([1]) { should be_instance_of(described_class) }
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'when response is a hash' do
|
97
|
+
before do
|
98
|
+
response.should_receive(:body).and_return(item)
|
99
|
+
end
|
100
|
+
let(:response) { double('response') }
|
101
|
+
let(:item) { Hash.new }
|
102
|
+
|
103
|
+
subject { described_class.from_response(response) }
|
104
|
+
|
105
|
+
it { should be_instance_of(described_class) }
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when response is a string' do
|
109
|
+
before do
|
110
|
+
response.should_receive(:body).and_return(item)
|
111
|
+
end
|
112
|
+
let(:response) { double('response') }
|
113
|
+
let(:item) { 'some string' }
|
114
|
+
|
115
|
+
subject { described_class.from_response(response) }
|
116
|
+
|
117
|
+
it { should be_nil }
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'when response is a number' do
|
121
|
+
before do
|
122
|
+
response.should_receive(:body).and_return(item)
|
123
|
+
end
|
124
|
+
let(:response) { double('response') }
|
125
|
+
let(:item) { 45 }
|
126
|
+
|
127
|
+
subject { described_class.from_response(response) }
|
128
|
+
|
129
|
+
it { should be_nil }
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'when response is a nil object' do
|
133
|
+
before do
|
134
|
+
response.should_receive(:body).and_return(item)
|
135
|
+
end
|
136
|
+
let(:response) { double('response') }
|
137
|
+
let(:item) { nil }
|
138
|
+
|
139
|
+
subject { described_class.from_response(response) }
|
140
|
+
|
141
|
+
it { should be_nil }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '.new' do
|
146
|
+
before do
|
147
|
+
described_class.attribute :attr1, :attr2
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'creating entity with no attributes' do
|
151
|
+
subject { entity }
|
152
|
+
let(:entity) { described_class.new }
|
153
|
+
|
154
|
+
it { should respond_to(:attr1) }
|
155
|
+
it { should respond_to(:attr2) }
|
156
|
+
|
157
|
+
its(:attr1) { should be_nil }
|
158
|
+
its(:attr2) { should be_nil }
|
159
|
+
|
160
|
+
it 'should raise error for invalid attributes' do
|
161
|
+
expect { subject.attr3 }.to raise_error(NameError)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should set attr1' do
|
165
|
+
subject.attr1 = 'attr1 value'
|
166
|
+
subject.attr1.should == 'attr1 value'
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should set attr2' do
|
170
|
+
subject.attr1 = 'attr2 value'
|
171
|
+
subject.attr1.should == 'attr2 value'
|
172
|
+
end
|
173
|
+
|
174
|
+
describe '#attributes' do
|
175
|
+
subject { entity.attributes }
|
176
|
+
|
177
|
+
it { should be_empty }
|
178
|
+
end
|
179
|
+
|
180
|
+
describe '#as_json' do
|
181
|
+
subject { entity.as_json }
|
182
|
+
|
183
|
+
it { should be_empty }
|
184
|
+
end
|
185
|
+
|
186
|
+
describe '#to_json' do
|
187
|
+
subject { JSON.parse(entity.to_json) }
|
188
|
+
|
189
|
+
it { should be_empty }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'creating entity with valid attributes' do
|
194
|
+
subject { entity }
|
195
|
+
let(:entity) { described_class.new(:attr1 => attr1, :attr2 => attr2) }
|
196
|
+
let(:attr1) { 'attr1 value' }
|
197
|
+
let(:attr2) { 'attr2 value' }
|
198
|
+
|
199
|
+
it { should respond_to(:attr1) }
|
200
|
+
it { should respond_to(:attr2) }
|
201
|
+
|
202
|
+
its(:attr1) { should == attr1 }
|
203
|
+
its(:attr2) { should == attr2 }
|
204
|
+
|
205
|
+
describe '#attributes' do
|
206
|
+
subject { entity.attributes }
|
207
|
+
|
208
|
+
it { should have(2).items }
|
209
|
+
it { should include(:attr1) }
|
210
|
+
it { should include(:attr2) }
|
211
|
+
it { should_not include(:attr3) }
|
212
|
+
end
|
213
|
+
|
214
|
+
describe '#as_json' do
|
215
|
+
subject { entity.as_json }
|
216
|
+
|
217
|
+
it { should have(2).items }
|
218
|
+
it { should include(:attr1) }
|
219
|
+
it { should include(:attr2) }
|
220
|
+
it { should_not include(:attr3) }
|
221
|
+
end
|
222
|
+
|
223
|
+
describe '#to_json' do
|
224
|
+
subject { JSON.parse(entity.to_json) }
|
225
|
+
|
226
|
+
it { should have(2).items }
|
227
|
+
it { should include('attr1') }
|
228
|
+
it { should include('attr2') }
|
229
|
+
it { should_not include('attr3') }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
context 'creating entity with invalid attributes' do
|
234
|
+
subject { entity }
|
235
|
+
let(:entity) { described_class.new(:attr1 => attr1, :attr2 => attr2, :attr3 => attr3) }
|
236
|
+
let(:attr1) { 'attr1 value' }
|
237
|
+
let(:attr2) { 'attr2 value' }
|
238
|
+
let(:attr3) { 'attr3 value' }
|
239
|
+
|
240
|
+
its(:attr1) { should == attr1 }
|
241
|
+
its(:attr2) { should == attr2 }
|
242
|
+
|
243
|
+
it 'should raise error for invalid attributes' do
|
244
|
+
expect { subject.attr3 }.to raise_error(NameError)
|
245
|
+
end
|
246
|
+
|
247
|
+
describe '#attributes' do
|
248
|
+
subject { entity.attributes }
|
249
|
+
|
250
|
+
it { should have(2).items }
|
251
|
+
it { should include(:attr1) }
|
252
|
+
it { should include(:attr2) }
|
253
|
+
it { should_not include(:attr3) }
|
254
|
+
end
|
255
|
+
|
256
|
+
describe '#as_json' do
|
257
|
+
subject { entity.as_json }
|
258
|
+
|
259
|
+
it { should have(2).items }
|
260
|
+
it { should include(:attr1) }
|
261
|
+
it { should include(:attr2) }
|
262
|
+
it { should_not include(:attr3) }
|
263
|
+
end
|
264
|
+
|
265
|
+
describe '#to_json' do
|
266
|
+
subject { JSON.parse(entity.to_json) }
|
267
|
+
|
268
|
+
it { should have(2).items }
|
269
|
+
it { should include('attr1') }
|
270
|
+
it { should include('attr2') }
|
271
|
+
it { should_not include('attr3') }
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|