neverbounce-api 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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +8 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +1161 -0
  6. data/.travis.yml +27 -0
  7. data/.yardopts +2 -0
  8. data/Gemfile +17 -0
  9. data/Gemfile.lock +48 -0
  10. data/LICENSE +9 -0
  11. data/README.md +235 -0
  12. data/lib/never_bounce/api/client.rb +259 -0
  13. data/lib/never_bounce/api/error.rb +21 -0
  14. data/lib/never_bounce/api/feature/basic_initialize.rb +20 -0
  15. data/lib/never_bounce/api/feature/eigencache.rb +48 -0
  16. data/lib/never_bounce/api/feature/igetset.rb +41 -0
  17. data/lib/never_bounce/api/feature/oattrs.rb +100 -0
  18. data/lib/never_bounce/api/feature/require_attr.rb +27 -0
  19. data/lib/never_bounce/api/request/account_info.rb +29 -0
  20. data/lib/never_bounce/api/request/base.rb +102 -0
  21. data/lib/never_bounce/api/request/jobs_create.rb +90 -0
  22. data/lib/never_bounce/api/request/jobs_delete.rb +34 -0
  23. data/lib/never_bounce/api/request/jobs_download.rb +34 -0
  24. data/lib/never_bounce/api/request/jobs_parse.rb +55 -0
  25. data/lib/never_bounce/api/request/jobs_results.rb +53 -0
  26. data/lib/never_bounce/api/request/jobs_search.rb +57 -0
  27. data/lib/never_bounce/api/request/jobs_start.rb +47 -0
  28. data/lib/never_bounce/api/request/jobs_status.rb +37 -0
  29. data/lib/never_bounce/api/request/single_check.rb +67 -0
  30. data/lib/never_bounce/api/response/account_info.rb +39 -0
  31. data/lib/never_bounce/api/response/account_info/job_counts.rb +26 -0
  32. data/lib/never_bounce/api/response/address_info.rb +43 -0
  33. data/lib/never_bounce/api/response/base.rb +15 -0
  34. data/lib/never_bounce/api/response/container.rb +126 -0
  35. data/lib/never_bounce/api/response/credits_info/base.rb +30 -0
  36. data/lib/never_bounce/api/response/credits_info/monthly.rb +16 -0
  37. data/lib/never_bounce/api/response/credits_info/paid.rb +20 -0
  38. data/lib/never_bounce/api/response/error_message.rb +17 -0
  39. data/lib/never_bounce/api/response/feature/job_status_fields.rb +68 -0
  40. data/lib/never_bounce/api/response/feature/job_status_fields/total.rb +46 -0
  41. data/lib/never_bounce/api/response/jobs_create.rb +10 -0
  42. data/lib/never_bounce/api/response/jobs_delete.rb +8 -0
  43. data/lib/never_bounce/api/response/jobs_download.rb +19 -0
  44. data/lib/never_bounce/api/response/jobs_parse.rb +10 -0
  45. data/lib/never_bounce/api/response/jobs_results.rb +34 -0
  46. data/lib/never_bounce/api/response/jobs_results/item.rb +65 -0
  47. data/lib/never_bounce/api/response/jobs_results/query.rb +20 -0
  48. data/lib/never_bounce/api/response/jobs_search.rb +33 -0
  49. data/lib/never_bounce/api/response/jobs_search/query.rb +16 -0
  50. data/lib/never_bounce/api/response/jobs_search/result.rb +13 -0
  51. data/lib/never_bounce/api/response/jobs_start.rb +10 -0
  52. data/lib/never_bounce/api/response/jobs_status.rb +11 -0
  53. data/lib/never_bounce/api/response/message.rb +32 -0
  54. data/lib/never_bounce/api/response/single_check.rb +68 -0
  55. data/lib/never_bounce/api/response/status_message.rb +17 -0
  56. data/lib/never_bounce/api/response/success_message.rb +12 -0
  57. data/lib/never_bounce/api/session.rb +141 -0
  58. data/lib/never_bounce/api/version.rb +4 -0
  59. data/lib/neverbounce-api.rb +4 -0
  60. data/lib/neverbounce.rb +3 -0
  61. data/neverbounce-api.gemspec +20 -0
  62. data/spec/lib/never_bounce/api/client_spec.rb +199 -0
  63. data/spec/lib/never_bounce/api/feature/basic_initialize_spec.rb +25 -0
  64. data/spec/lib/never_bounce/api/feature/eigencache_spec.rb +28 -0
  65. data/spec/lib/never_bounce/api/feature/igetset_spec.rb +45 -0
  66. data/spec/lib/never_bounce/api/feature/oattrs_spec.rb +72 -0
  67. data/spec/lib/never_bounce/api/feature/require_attr_spec.rb +25 -0
  68. data/spec/lib/never_bounce/api/request/account_info_spec.rb +29 -0
  69. data/spec/lib/never_bounce/api/request/base_spec.rb +6 -0
  70. data/spec/lib/never_bounce/api/request/jobs_create_spec.rb +89 -0
  71. data/spec/lib/never_bounce/api/request/jobs_delete_spec.rb +31 -0
  72. data/spec/lib/never_bounce/api/request/jobs_download_spec.rb +31 -0
  73. data/spec/lib/never_bounce/api/request/jobs_parse_spec.rb +44 -0
  74. data/spec/lib/never_bounce/api/request/jobs_results_spec.rb +42 -0
  75. data/spec/lib/never_bounce/api/request/jobs_search_spec.rb +40 -0
  76. data/spec/lib/never_bounce/api/request/jobs_start_spec.rb +44 -0
  77. data/spec/lib/never_bounce/api/request/jobs_status_spec.rb +31 -0
  78. data/spec/lib/never_bounce/api/request/single_check_spec.rb +44 -0
  79. data/spec/lib/never_bounce/api/response/account_info/job_counts_spec.rb +7 -0
  80. data/spec/lib/never_bounce/api/response/account_info_spec.rb +9 -0
  81. data/spec/lib/never_bounce/api/response/address_info_spec.rb +7 -0
  82. data/spec/lib/never_bounce/api/response/base_spec.rb +6 -0
  83. data/spec/lib/never_bounce/api/response/container_spec.rb +142 -0
  84. data/spec/lib/never_bounce/api/response/credits_info/base_spec.rb +31 -0
  85. data/spec/lib/never_bounce/api/response/credits_info/monthly_spec.rb +11 -0
  86. data/spec/lib/never_bounce/api/response/credits_info/paid_spec.rb +11 -0
  87. data/spec/lib/never_bounce/api/response/feature/job_status_fields/total_spec.rb +7 -0
  88. data/spec/lib/never_bounce/api/response/feature/job_status_fields_spec.rb +42 -0
  89. data/spec/lib/never_bounce/api/response/jobs_create_spec.rb +7 -0
  90. data/spec/lib/never_bounce/api/response/jobs_delete_spec.rb +7 -0
  91. data/spec/lib/never_bounce/api/response/jobs_download_spec.rb +7 -0
  92. data/spec/lib/never_bounce/api/response/jobs_parse_spec.rb +7 -0
  93. data/spec/lib/never_bounce/api/response/jobs_results/jobs_results_item_spec.rb +7 -0
  94. data/spec/lib/never_bounce/api/response/jobs_results/jobs_results_query_spec.rb +7 -0
  95. data/spec/lib/never_bounce/api/response/jobs_results_spec.rb +7 -0
  96. data/spec/lib/never_bounce/api/response/jobs_search/jobs_search_query_spec.rb +7 -0
  97. data/spec/lib/never_bounce/api/response/jobs_search/jobs_search_result_spec.rb +7 -0
  98. data/spec/lib/never_bounce/api/response/jobs_search_spec.rb +7 -0
  99. data/spec/lib/never_bounce/api/response/jobs_start_spec.rb +7 -0
  100. data/spec/lib/never_bounce/api/response/jobs_status_spec.rb +7 -0
  101. data/spec/lib/never_bounce/api/response/single_check_spec.rb +7 -0
  102. data/spec/lib/never_bounce/api/response/spec_helper.rb +8 -0
  103. data/spec/lib/never_bounce/api/session_spec.rb +140 -0
  104. data/spec/lib/never_bounce/api_spec.rb +8 -0
  105. data/spec/spec_helper.rb +49 -0
  106. data/spec/spec_support/include_dir_context.rb +22 -0
  107. data/spec/spec_support/simplecov.rb +18 -0
  108. metadata +210 -0
@@ -0,0 +1,21 @@
1
+
2
+ module NeverBounce; module API
3
+ # Base class for all errors.
4
+ # @abstract
5
+ class Error < StandardError; end
6
+
7
+ # Incorrect attribute usage.
8
+ class AttributeError < Error; end
9
+
10
+ # Data format errors happening in our land.
11
+ class FormatError < Error; end
12
+
13
+ # Something went wrong with the request.
14
+ class RequestError < Error; end
15
+ end; end
16
+
17
+ #
18
+ # Implementation notes:
19
+ #
20
+ # * None of these exceptions should be suppressed in end-user script.
21
+ # Either they must be handled or they should crash the end-user script with a full backtrace.
@@ -0,0 +1,20 @@
1
+
2
+ module NeverBounce; module API; module Feature
3
+ # Provide the basic method <tt>#initialize</tt> to the owner class.
4
+ # @see InstanceMethods#initialize
5
+ module BasicInitialize
6
+ # @param owner [Class]
7
+ # @return [nil]
8
+ def self.load(owner)
9
+ return if owner < InstanceMethods
10
+ owner.send(:include, InstanceMethods)
11
+ end
12
+
13
+ module InstanceMethods
14
+ # See source for details.
15
+ def initialize(attrs = {})
16
+ attrs.each { |k, v| public_send("#{k}=", v) }
17
+ end
18
+ end
19
+ end
20
+ end; end; end
@@ -0,0 +1,48 @@
1
+
2
+ module NeverBounce; module API; module Feature
3
+ # Eigenclass (singleton class)-based cache which allows to handle "hidden" instance variables.
4
+ # @see InstanceMethods#_cache
5
+ module Eigencache
6
+ # @param owner [Class]
7
+ # @return [nil]
8
+ def self.load(owner)
9
+ return if owner < InstanceMethods
10
+ owner.send(:include, InstanceMethods)
11
+ end
12
+
13
+ module InstanceMethods
14
+ # Hidden cache.
15
+ #
16
+ # def body
17
+ # _cache[:body] ||= File.read("my-bulky-body.csv")
18
+ # end
19
+ #
20
+ # def body=(v)
21
+ # _cache[:body] = v
22
+ # end
23
+ #
24
+ # @return [Hash]
25
+ def _cache
26
+ if eigen.instance_variable_defined?(k = :@cache)
27
+ eigen.instance_variable_get(k)
28
+ else
29
+ eigen.instance_variable_set(k, {})
30
+ end
31
+ end
32
+
33
+ #---------------------------------------
34
+ private
35
+
36
+ # Object's eigenclass (singleton class).
37
+ # @return [Class]
38
+ def eigen
39
+ class << self; self; end
40
+ end
41
+ end
42
+ end
43
+ end; end; end
44
+
45
+ #
46
+ # Implementation notes:
47
+ #
48
+ # * As of Ruby 2.1 `Object#singleton_class` is introduced. We use our own compatible implementation, `eigen`.
@@ -0,0 +1,41 @@
1
+
2
+ module NeverBounce; module API; module Feature
3
+ # @see InstanceMethods#igetset
4
+ module Igetset
5
+ # @param owner [Class]
6
+ # @return [nil]
7
+ def self.load(owner)
8
+ return if owner < InstanceMethods
9
+ owner.send(:include, InstanceMethods)
10
+ end
11
+
12
+ module InstanceMethods
13
+ private
14
+
15
+ # Get/set an OTF instance variable of any type.
16
+ #
17
+ # Ruby's <tt>||=</tt> works nicely with object instances, but requires special bulky treatment for <tt>nil</tt> and <tt>false</tt>.
18
+ # For example, this will cause a hidden glitch since <tt>==</tt> can evaluate to <tt>false</tt>:
19
+ #
20
+ # @is_verbose ||= begin
21
+ # # This clause will be evaluated *every time* if its value is `false`.
22
+ # ENV["VERBOSE"] == "y"
23
+ # end
24
+ #
25
+ # There's a number of solutions to this problem, all of which involve calling <tt>instance_variable_*</tt> a few times per attribute accessor.
26
+ #
27
+ # <tt>igetset</tt> does this job for you. All you have to do is specify a block to compute the value.
28
+ #
29
+ # igetset(:is_verbose) { ENV["VERBOSE"] == "y" }
30
+ #
31
+ # See source for details.
32
+ def igetset(name, &compute)
33
+ if instance_variable_defined?(k = "@#{name}")
34
+ instance_variable_get(k)
35
+ else
36
+ instance_variable_set(k, compute.call)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end; end; end
@@ -0,0 +1,100 @@
1
+
2
+ module NeverBounce; module API; module Feature
3
+ # Declare and manage on-the-fly (OTF) attributes of the class, codenamed "oattrs".
4
+ # @see ClassMethods#oattr
5
+ # @see ClassMethods#oattrs
6
+ # @see InstanceMethods#touch
7
+ module Oattrs
8
+ # @return [nil]
9
+ def self.load(owner)
10
+ return if owner < InstanceMethods
11
+ owner.extend(ClassMethods)
12
+ owner.send(:include, InstanceMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ # Return oattrs declared so far, recurse all superclasses.
17
+ #
18
+ # oattrs # => [:first_name, :last_name]
19
+ #
20
+ # @!attribute oattrs
21
+ # @return [Array]
22
+ def oattrs
23
+ @attrs ||= if superclass.respond_to?(m = :oattrs)
24
+ superclass.send(m).dup
25
+ else
26
+ []
27
+ end
28
+ end
29
+
30
+ def oattrs=(ar)
31
+ @oattrs = ar
32
+ end
33
+
34
+ # Declare an OTF attribute.
35
+ #
36
+ # class Klass
37
+ # oattr :first_name, :writer
38
+ # oattr :goods, :custom
39
+ #
40
+ # def first_name
41
+ # @first_name ||= ENV["FIRST_NAME"]
42
+ # end
43
+ #
44
+ # def goods
45
+ # @goods ||= ...
46
+ #
47
+ # def goods=(ar)
48
+ # @goods = ar
49
+ # ...
50
+ #
51
+ # @return [Symbol] <tt>name</tt>.
52
+ # @see InstanceMethods#touch
53
+ def oattr(name, type)
54
+ case type
55
+ when :custom
56
+ # Do nothing, just register attr below.
57
+ when :writer
58
+ attr_writer name
59
+ else
60
+ raise ArgumentError, "Unknown type: #{type.inspect}"
61
+ end
62
+
63
+ # Register and return.
64
+ name.tap { oattrs << name}
65
+ end
66
+ end
67
+
68
+ module InstanceMethods
69
+ # Load all oattrs by "touching" them.
70
+ #
71
+ # irb> resp = client.jobs_delete(job_id: 353701)
72
+ # => #<NeverBounce::API::Response::ErrorMessage:0x0056245978aec8>
73
+ # irb> resp.touch
74
+ # => #<NeverBounce::API::Response::ErrorMessage:0x0056245978aec8 @message="Invalid job ID 353701", @execution_time=15, @status="general_failure">
75
+ #
76
+ # @return [self]
77
+ def touch
78
+ self.class.oattrs.each do |name|
79
+ v = public_send(name)
80
+
81
+ # Touch recursively. Support simple collections.
82
+ if v.respond_to? :touch
83
+ v.touch
84
+ elsif v.is_a? Array
85
+ v.each { |r| r.touch if r.respond_to? :touch }
86
+ end
87
+ end
88
+
89
+ self
90
+ end
91
+ end
92
+ end
93
+ end; end; end
94
+
95
+ #
96
+ # Implementation notes:
97
+ #
98
+ # * Originally I've used `attr` and `attrs` as method names.
99
+ # Sadly enough, YARD choked on `attr ...` with no reasonable way to work around it and tell it to ignore these statements.
100
+ # That led me to the conclusion that `attr` acts more like a language construct, so I'd rather not override it, but make my own.
@@ -0,0 +1,27 @@
1
+
2
+ require "never_bounce/api/error"
3
+
4
+ module NeverBounce; module API; module Feature
5
+ # Provide attribute validation method(s) to the owner class.
6
+ # @see InstanceMethods#initialize
7
+ module RequireAttr
8
+ # @param owner [Class]
9
+ # @return [nil]
10
+ def self.load(owner)
11
+ return if owner < InstanceMethods
12
+ owner.send(:include, InstanceMethods)
13
+ end
14
+
15
+ module InstanceMethods
16
+ private
17
+
18
+ # Require attribute to be set. Return attribute value.
19
+ # @return [mixed]
20
+ def require_attr(name)
21
+ send(name).tap do |_|
22
+ raise AttributeError, "Attribute must be set: #{name}" if _.nil?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end; end; end
@@ -0,0 +1,29 @@
1
+
2
+ require "never_bounce/api/response/account_info"
3
+
4
+ require_relative "base"
5
+
6
+ module NeverBounce; module API; module Request
7
+ class AccountInfo < Base
8
+ # @return [Symbol]
9
+ def self.http_method
10
+ :get
11
+ end
12
+
13
+ # @return [String]
14
+ def self.path
15
+ "account/info"
16
+ end
17
+
18
+ # @return [Response::AccountInfo]
19
+ def self.response_klass
20
+ Response::AccountInfo
21
+ end
22
+
23
+ def to_h
24
+ {
25
+ key: require_attr(:api_key),
26
+ }
27
+ end
28
+ end
29
+ end; end; end
@@ -0,0 +1,102 @@
1
+
2
+ require "never_bounce/api/feature/basic_initialize"
3
+ require "never_bounce/api/feature/require_attr"
4
+
5
+ module NeverBounce; module API; module Request
6
+ # @abstract
7
+ # @see API::Feature::BasicInitialize
8
+ class Base
9
+ API::Feature::BasicInitialize.load(self)
10
+ API::Feature::RequireAttr.load(self)
11
+
12
+ # User's API key.
13
+ # @return [String]
14
+ attr_accessor :api_key
15
+ attr_writer :api_url, :headers, :user_agent
16
+
17
+ # Custom API URL. Default is <tt>https://api.neverbounce.com/v4</tt>.
18
+ # @return [String]
19
+ def api_url
20
+ @api_url ||= "https://api.neverbounce.com/v4"
21
+ end
22
+
23
+ # @!attribute headers
24
+ # @return [Array]
25
+ def headers
26
+ {
27
+ "Content-Type" => "application/json",
28
+ "User-Agent" => user_agent,
29
+ }
30
+ end
31
+
32
+ # <tt>:get</tt>, <tt>:post</tt> or whatever.
33
+ # @abstract
34
+ # @return [Symbol]
35
+ # @return [String]
36
+ def self.http_method
37
+ raise NotImplementedError, "Redefine `self.http_method` in your class: #{self}"
38
+ end
39
+
40
+ # Request path on server, e.g. <tt>"jobs/parse"</tt>.
41
+ # @abstract
42
+ # @return [String]
43
+ def self.path
44
+ raise NotImplementedError, "Redefine `self.path` in your class: #{self}"
45
+ end
46
+
47
+ # @abstract
48
+ # @return [Class]
49
+ def self.response_klass
50
+ raise NotImplementedError, "Redefine `self.response_klass` in your class: #{self}"
51
+ end
52
+
53
+ # Build arguments for cURL OS command.
54
+ # @return [Array]
55
+ def to_curl
56
+ # NOTE: I consider we should use long options to avoid ambiguity of ones like `-u` etc.
57
+ @curl ||= begin
58
+ ar = [
59
+ "--request", self.class.http_method.to_s.upcase,
60
+ "--url", "#{api_url}/#{self.class.path}",
61
+ ]
62
+
63
+ ar += headers.reject { |k,| k == "User-Agent" }.flat_map do |k, v|
64
+ ["--header", "#{k}: #{v}"]
65
+ end
66
+
67
+ ar += ["--data-binary", to_h.to_json]
68
+
69
+ ar
70
+ end
71
+ end
72
+
73
+ # Build a <tt>Hash</tt> representation of request data.
74
+ # @abstract
75
+ # @return [Hash]
76
+ def to_h
77
+ raise NotImplementedError, "Redefine `to_h` in your class: #{self.class}"
78
+ end
79
+
80
+ # Build argumentsfor <tt>Httparty</tt> invocation.
81
+ # @return [Array]
82
+ def to_httparty
83
+ [
84
+ self.class.http_method, # E.g. `:get`.
85
+ "#{api_url}/#{self.class.path}",
86
+ {
87
+ body: to_h.to_json,
88
+ headers: headers,
89
+ }
90
+ ]
91
+ end
92
+
93
+ # @!attribute user_agent
94
+ # @return [String]
95
+ def user_agent
96
+ @user_agent ||= [
97
+ "NeverBounceApi-Ruby/#{API::VERSION} (#{RUBY_PLATFORM})",
98
+ "Ruby/#{RUBY_VERSION} (p #{RUBY_PATCHLEVEL}; rev #{RUBY_REVISION})",
99
+ ].join(" ")
100
+ end
101
+ end
102
+ end; end; end
@@ -0,0 +1,90 @@
1
+
2
+ require_relative "base"
3
+
4
+ module NeverBounce; module API; module Request
5
+ class JobsCreate < Base
6
+ # @return [Boolean]
7
+ attr_accessor :auto_parse
8
+
9
+ # @return [Boolean]
10
+ attr_accessor :auto_start
11
+
12
+ # @return [String]
13
+ attr_accessor :filename
14
+
15
+ # Input specification.
16
+ # @return [String] URL if <tt>input_location</tt> is <tt>"remote".
17
+ # @return [Array<Array<email, name>>] Structure if <tt>input_location</tt> is "supplied".
18
+ attr_accessor :input
19
+
20
+ # @return [String]
21
+ attr_accessor :input_location
22
+
23
+ # @return [Boolean]
24
+ attr_accessor :run_sample
25
+
26
+ # @return [Symbol]
27
+ def self.http_method
28
+ :post
29
+ end
30
+
31
+ # @return [String]
32
+ def self.path
33
+ "jobs/create"
34
+ end
35
+
36
+ # @return [Response::JobsCreate]
37
+ def self.response_klass
38
+ Response::JobsCreate
39
+ end
40
+
41
+ # Return a ready-to-merge mode attributes hash.
42
+ # @return [Hash]
43
+ def mode_h
44
+ @mode_h ||= {}.tap do |_|
45
+ unless (v = auto_start).nil?
46
+ _[:auto_start] = v
47
+ end
48
+
49
+ unless (v = auto_parse).nil?
50
+ _[:auto_parse] = v
51
+ end
52
+
53
+ unless (v = run_sample).nil?
54
+ _[:run_sample] = v
55
+ end
56
+ end
57
+ end
58
+
59
+ # @return [Hash]
60
+ def to_h
61
+ input = require_attr(:input)
62
+ input_location = require_attr(:input_location)
63
+
64
+ # Validate `input_location` and `input`.
65
+
66
+ if not ["remote_url", "supplied"].include? input_location
67
+ raise AttributeError, "Unknown `input_location`: #{input_location.inspect}"
68
+ end
69
+
70
+ if input_location == "supplied"
71
+ raise AttributeError, "Invalid `input` for `input_location` == #{input_location.inspect}: #{input.inspect}" if not input.is_a? Array
72
+
73
+ # Skim through elements of `input`, raise if invalid structure detected, with details.
74
+ input.each do |elem|
75
+ em = [AttributeError, "Invalid `input` element: #{elem.inspect}"]
76
+ raise(*em) unless elem.is_a? Array
77
+ raise(*em) unless elem.map(&:class) == [String, String]
78
+ end
79
+ end
80
+
81
+ # Result.
82
+ {
83
+ input: input,
84
+ input_location: input_location,
85
+ filename: require_attr(:filename),
86
+ key: require_attr(:api_key),
87
+ }.merge(mode_h)
88
+ end
89
+ end
90
+ end; end; end