Skip to content

Commit 2d6f965

Browse files
committed
Merge branch 'release/2.0.0'
* release/2.0.0: Updated readme, changed slow requests to return a BlazeVerify::Timeout error. Updated translations to not be namedspaced and also made it so we send accept_all to the API only when necessary. Added options validation to validator and additional test coverage. Added email_validator_test Added active record validator, removed faraday version restriction and updated readme.
2 parents d040687 + 5a116e1 commit 2d6f965

File tree

11 files changed

+263
-38
lines changed

11 files changed

+263
-38
lines changed

Gemfile.lock

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
11
PATH
22
remote: .
33
specs:
4-
blazeverify (1.3.1)
5-
faraday (~> 0.13)
4+
blazeverify (1.3.2)
5+
faraday
66
faraday_middleware
77
net-http-persistent
88

99
GEM
1010
remote: https://rubygems.org/
1111
specs:
12+
activemodel (6.0.3.4)
13+
activesupport (= 6.0.3.4)
14+
activesupport (6.0.3.4)
15+
concurrent-ruby (~> 1.0, >= 1.0.2)
16+
i18n (>= 0.7, < 2)
17+
minitest (~> 5.1)
18+
tzinfo (~> 1.1)
19+
zeitwerk (~> 2.2, >= 2.2.2)
1220
ansi (1.5.0)
1321
awesome_print (1.8.0)
1422
builder (3.2.3)
1523
coderay (1.1.2)
16-
connection_pool (2.2.2)
17-
faraday (0.17.0)
24+
concurrent-ruby (1.1.7)
25+
connection_pool (2.2.3)
26+
faraday (1.1.0)
1827
multipart-post (>= 1.2, < 3)
19-
faraday_middleware (0.13.1)
20-
faraday (>= 0.7.4, < 1.0)
28+
ruby2_keywords
29+
faraday_middleware (1.0.0)
30+
faraday (~> 1.0)
31+
i18n (1.8.5)
32+
concurrent-ruby (~> 1.0)
2133
method_source (0.9.2)
2234
minitest (5.11.3)
2335
minitest-reporters (1.3.6)
@@ -26,18 +38,24 @@ GEM
2638
minitest (>= 5.0)
2739
ruby-progressbar
2840
multipart-post (2.1.1)
29-
net-http-persistent (3.1.0)
41+
net-http-persistent (4.0.0)
3042
connection_pool (~> 2.2)
3143
pry (0.12.2)
3244
coderay (~> 1.1.0)
3345
method_source (~> 0.9.0)
3446
rake (13.0.1)
3547
ruby-progressbar (1.10.1)
48+
ruby2_keywords (0.0.2)
49+
thread_safe (0.3.6)
50+
tzinfo (1.2.8)
51+
thread_safe (~> 0.1)
52+
zeitwerk (2.4.1)
3653

3754
PLATFORMS
3855
ruby
3956

4057
DEPENDENCIES
58+
activemodel
4159
awesome_print
4260
blazeverify!
4361
bundler

README.md

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
This is the official ruby wrapper for the Blaze Verify API.
77

8+
It also includes an Active Record (Rails) validator to verify email attributes.
9+
810
## Documentation
911

1012
See the [Ruby API docs](https://blazeverify.com/docs/api/?ruby).
@@ -47,18 +49,14 @@ BlazeVerify.verify('jarrett@blazeverify.com')
4749

4850
#### Slow Email Server Handling
4951

50-
Some email servers are slow to respond. As a result the timeout may be reached
52+
Some email servers are slow to respond. As a result, the timeout may be reached
5153
before we are able to complete the verification process. If this happens, the
52-
verification will continue in the background on our servers. We recommend
53-
sleeping for at least one second and trying your request again. Re-requesting
54-
the same verification with the same options will not impact your credit
55-
allocation within a 5 minute window.
56-
57-
```ruby
58-
{
59-
"message" => "Your request is taking longer than normal. Please send your request again."
60-
}
61-
```
54+
verification will continue in the background on our servers, and a
55+
`BlazeVerify::TimeoutError` will be raised. We recommend sleeping for at least
56+
one second and trying your request again. Re-requesting the same verification
57+
with the same options will not impact your credit allocation within a 5 minute
58+
window. You can test this behavior using a test key and the special
59+
email `slow@example.com`.
6260

6361
### Batch Verification
6462

@@ -98,6 +96,34 @@ batch.status.reason_counts
9896
batch.complete?
9997
```
10098

99+
### Active Record Validator
100+
101+
Define a validator on an Active Record model for your email attribute(s).
102+
It'll validate the attribute only when it's present and has changed.
103+
104+
#### Options
105+
106+
* `smtp`, `timeout`: Passed directly to API as options.
107+
* `states`: An array of states you'd like to be considered valid.
108+
* `free`, `role`, `disposable`, `accept_all`: If you'd like any of these to be valid.
109+
110+
```ruby
111+
validates :email, email: {
112+
smtp: true, states: %i[deliverable risky unknown],
113+
free: true, role: true, disposable: false, accept_all: true, timeout: 3
114+
}
115+
```
116+
117+
#### Access Verification Result
118+
119+
You can define an `attr_accessor` with the following format to gain
120+
access to the verification result.
121+
122+
```ruby
123+
# [attribute_name]_verification_result
124+
attr_accessor :email_verification_result
125+
```
126+
101127
## Development
102128

103129
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

blazeverify.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
2727
end
2828
s.require_paths = ['lib']
2929

30-
s.add_dependency 'faraday', '~> 0.13'
30+
s.add_dependency 'faraday'
3131
s.add_dependency 'faraday_middleware'
3232
s.add_dependency 'net-http-persistent'
3333
s.add_development_dependency 'bundler'
@@ -36,4 +36,5 @@ Gem::Specification.new do |s|
3636
s.add_development_dependency 'awesome_print'
3737
s.add_development_dependency 'minitest', '~> 5.0'
3838
s.add_development_dependency 'minitest-reporters'
39+
s.add_development_dependency 'activemodel'
3940
end

config/locales/en.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
en:
2+
errors:
3+
messages:
4+
undeliverable: is undeliverable
5+
risky: is risky
6+
unknown: is unknown
7+
free: is a free address
8+
role: is a role address
9+
disposable: is a disposable address
10+
accept_all: is an accept-all address

lib/blazeverify.rb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
require 'blazeverify/resources/account'
88
require 'blazeverify/resources/batch_status'
99
require 'blazeverify/resources/verification'
10+
if defined?(ActiveModel)
11+
require 'blazeverify/email_validator'
12+
I18n.load_path += Dir.glob(File.expand_path('../../config/locales/**/*', __FILE__))
13+
end
1014

1115
module BlazeVerify
1216
@max_network_retries = 1
@@ -26,7 +30,9 @@ def verify(email, smtp: nil, accept_all: nil, timeout: nil)
2630
response = client.request(:get, 'verify', opts)
2731

2832
if response.status == 249
29-
response.body
33+
raise BlazeVerify::TimeoutError.new(
34+
code: response.status, message: response.body
35+
)
3036
else
3137
Verification.new(response.body)
3238
end
@@ -37,4 +43,23 @@ def account
3743
response = client.request(:get, 'account')
3844
Account.new(response.body)
3945
end
46+
47+
48+
class Error < StandardError
49+
attr_accessor :code, :message
50+
51+
def initialize(code: nil, message: nil)
52+
@code = code
53+
@message = message
54+
end
55+
end
56+
class BadRequestError < Error; end
57+
class UnauthorizedError < Error; end
58+
class PaymentRequiredError < Error; end
59+
class ForbiddenError < Error; end
60+
class NotFoundError < Error; end
61+
class TooManyRequestsError < Error; end
62+
class InternalServerError < Error; end
63+
class ServiceUnavailableError < Error; end
64+
class TimeoutError < Error; end
4065
end

lib/blazeverify/client.rb

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,4 @@ def self.should_retry?(error, num_retries)
6767
false
6868
end
6969
end
70-
71-
class Error < StandardError
72-
attr_accessor :code, :message
73-
74-
def initialize(code: nil, message: nil)
75-
@code = code
76-
@message = message
77-
end
78-
end
79-
class BadRequestError < Error; end
80-
class UnauthorizedError < Error; end
81-
class PaymentRequiredError < Error; end
82-
class ForbiddenError < Error; end
83-
class NotFoundError < Error; end
84-
class TooManyRequestsError < Error; end
85-
class InternalServerError < Error; end
86-
class ServiceUnavailableError < Error; end
8770
end

lib/blazeverify/email_validator.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# ActiveRecord validator for validating an email address with Blaze Verify
2+
#
3+
# Usage:
4+
# validates :email, presence: true, email: {
5+
# smtp: true, states: %i[deliverable risky unknown],
6+
# free: true, role: true, disposable: false, accept_all: true,
7+
# timeout: 3
8+
# }
9+
#
10+
# Define an attr_accessor to access verification results.
11+
# attr_accessor :email_verification_result
12+
#
13+
class EmailValidator < ActiveModel::EachValidator
14+
15+
def validate_each(record, attribute, value)
16+
smtp = boolean_option_or_raise_error(:smtp, true)
17+
18+
states = options.fetch(:states, %i(deliverable risky unknown))
19+
allowed_states = %i[deliverable undeliverable risky unknown]
20+
unless (states - allowed_states).empty?
21+
raise ArgumentError, ":states must be an array of symbols containing "\
22+
"any or all of :#{allowed_states.join(', :')}"
23+
end
24+
25+
free = boolean_option_or_raise_error(:free, true)
26+
role = boolean_option_or_raise_error(:role, true)
27+
disposable = boolean_option_or_raise_error(:disposable, false)
28+
accept_all = boolean_option_or_raise_error(:accept_all, true)
29+
30+
timeout = options.fetch(:timeout, 3)
31+
unless timeout.is_a?(Integer) && timeout > 1
32+
raise ArgumentError, ":timeout must be an Integer greater than 1"
33+
end
34+
35+
return if record.errors[attribute].present?
36+
return unless value.present?
37+
return unless record.changes[attribute].present?
38+
39+
api_options = { timeout: timeout, smtp: smtp }
40+
api_options[:accept_all] = true unless accept_all
41+
ev = BlazeVerify.verify(value, api_options)
42+
43+
result_accessor = "#{attribute}_verification_result"
44+
if record.respond_to?(result_accessor)
45+
record.instance_variable_set("@#{result_accessor}", ev)
46+
end
47+
48+
error ||= ev.state.to_sym unless states.include?(ev.state.to_sym)
49+
error ||= :free if ev.free? && !free
50+
error ||= :role if ev.role? && !role
51+
error ||= :disposable if ev.disposable? && !disposable
52+
error ||= :accept_all if ev.accept_all? && !accept_all
53+
54+
record.errors.add(attribute, error) if error
55+
rescue BlazeVerify::Error
56+
# silence errors
57+
end
58+
59+
private
60+
61+
def boolean_option_or_raise_error(name, default)
62+
option = options.fetch(name, default)
63+
unless [true, false].include?(option)
64+
raise ArgumentError, ":#{name} must by a Boolean"
65+
end
66+
67+
option
68+
end
69+
70+
end

lib/blazeverify/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module BlazeVerify
2-
VERSION = '1.3.1'
2+
VERSION = '2.0.0'
33
end

test/blazeverify_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,10 @@ def test_name_and_gender
6161
end
6262
end
6363

64+
def test_slow_verification
65+
assert_raises(BlazeVerify::TimeoutError) do
66+
BlazeVerify.verify('slow@example.com')
67+
end
68+
end
69+
6470
end

0 commit comments

Comments
 (0)