postcode-anywhere 1.0.1

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +247 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +72 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +319 -0
  9. data/Rakefile +2 -0
  10. data/config/pre_commit.yml +7 -0
  11. data/lib/postcode_anywhere.rb +10 -0
  12. data/lib/postcode_anywhere/bank_account_validation/bank_branch.rb +45 -0
  13. data/lib/postcode_anywhere/bank_account_validation/client.rb +13 -0
  14. data/lib/postcode_anywhere/bank_account_validation/interactive.rb +39 -0
  15. data/lib/postcode_anywhere/bank_account_validation/validation_result.rb +80 -0
  16. data/lib/postcode_anywhere/capture_plus/client.rb +13 -0
  17. data/lib/postcode_anywhere/capture_plus/interactive.rb +84 -0
  18. data/lib/postcode_anywhere/capture_plus/query_type.rb +8 -0
  19. data/lib/postcode_anywhere/capture_plus/retrieve_result.rb +45 -0
  20. data/lib/postcode_anywhere/capture_plus/search_result.rb +39 -0
  21. data/lib/postcode_anywhere/cleanse_plus/cleansed_address.rb +98 -0
  22. data/lib/postcode_anywhere/cleanse_plus/client.rb +13 -0
  23. data/lib/postcode_anywhere/cleanse_plus/interactive.rb +24 -0
  24. data/lib/postcode_anywhere/client.rb +86 -0
  25. data/lib/postcode_anywhere/configuration.rb +47 -0
  26. data/lib/postcode_anywhere/email_validation/client.rb +13 -0
  27. data/lib/postcode_anywhere/email_validation/interactive.rb +25 -0
  28. data/lib/postcode_anywhere/email_validation/validation_result.rb +24 -0
  29. data/lib/postcode_anywhere/error.rb +118 -0
  30. data/lib/postcode_anywhere/model_base.rb +72 -0
  31. data/lib/postcode_anywhere/request.rb +33 -0
  32. data/lib/postcode_anywhere/response/parse_json.rb +100 -0
  33. data/lib/postcode_anywhere/response/raise_error.rb +19 -0
  34. data/lib/postcode_anywhere/utils.rb +15 -0
  35. data/lib/postcode_anywhere/version.rb +3 -0
  36. data/postcode_anywhere.gemspec +31 -0
  37. data/spec/postcode_anywhere/bank_account_validation/interactive_spec.rb +82 -0
  38. data/spec/postcode_anywhere/capture_plus/interactive_spec.rb +240 -0
  39. data/spec/postcode_anywhere/cleanse_plus/interactive_spec.rb +65 -0
  40. data/spec/postcode_anywhere/client_spec.rb +124 -0
  41. data/spec/postcode_anywhere/configuration_spec.rb +62 -0
  42. data/spec/postcode_anywhere/email_validation/interactive_spec.rb +30 -0
  43. data/spec/postcode_anywhere/error_spec.rb +70 -0
  44. data/spec/postcode_anywhere/fixtures/bank_account_retrieve_by_sort.json +1 -0
  45. data/spec/postcode_anywhere/fixtures/bank_account_validate_account.json +1 -0
  46. data/spec/postcode_anywhere/fixtures/capture_plus_retrieve.json +1 -0
  47. data/spec/postcode_anywhere/fixtures/capture_plus_search.json +1 -0
  48. data/spec/postcode_anywhere/fixtures/cleanse_address_multi.json +1 -0
  49. data/spec/postcode_anywhere/fixtures/cleanse_address_single.json +1 -0
  50. data/spec/postcode_anywhere/fixtures/email_validation_validate_email.json +1 -0
  51. data/spec/postcode_anywhere/model_base_spec.rb +10 -0
  52. data/spec/spec_helper.rb +38 -0
  53. metadata +281 -0
@@ -0,0 +1,13 @@
1
+ require 'postcode_anywhere/client'
2
+ require 'postcode_anywhere/cleanse_plus/interactive'
3
+
4
+ module PostcodeAnywhere
5
+ module CleansePlus
6
+ class Client < ::PostcodeAnywhere::Client
7
+ include PostcodeAnywhere::CleansePlus::Interactive
8
+ def initialize(options = {})
9
+ super(options)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require 'postcode_anywhere/utils'
2
+ require 'postcode_anywhere/cleanse_plus/cleansed_address'
3
+
4
+ module PostcodeAnywhere
5
+ module CleansePlus
6
+ module Interactive
7
+ include ::PostcodeAnywhere::Utils
8
+
9
+ API_VERSION = '1.00'
10
+
11
+ CLEANSE_ADDRESS_ENDPOINT = "CleansePlus/Interactive/Cleanse/v#{API_VERSION}/json.ws"
12
+
13
+ def address_candidates_for(address, options = {})
14
+ options.merge!('Address' => address)
15
+ perform_with_objects(
16
+ :get,
17
+ CLEANSE_ADDRESS_ENDPOINT,
18
+ options,
19
+ PostcodeAnywhere::CleansePlus::CleansedAddress
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,86 @@
1
+ require 'faraday'
2
+ require 'faraday/request/multipart'
3
+ require 'json'
4
+ require 'timeout'
5
+ require 'postcode_anywhere/error'
6
+ require 'postcode_anywhere/response/raise_error'
7
+ require 'postcode_anywhere/response/parse_json'
8
+
9
+ module PostcodeAnywhere
10
+ class Client
11
+ attr_accessor(*Configuration::VALID_CONFIG_KEYS)
12
+
13
+ def initialize(options = {})
14
+ merged_options = PostcodeAnywhere.options.merge(options)
15
+
16
+ Configuration::VALID_CONFIG_KEYS.each do |key|
17
+ send("#{key}=", merged_options[key])
18
+ end
19
+ end
20
+
21
+ # Perform an HTTP GET request
22
+ def get(path, body_hash = {}, params = {})
23
+ request(:get, path, params, body_hash)
24
+ end
25
+
26
+ # Perform an HTTP POST request
27
+ def post(path, body_hash = {}, params = {})
28
+ request(:post, path, params, body_hash)
29
+ end
30
+
31
+ def connection_options
32
+ @connection_options ||= {
33
+ builder: middleware,
34
+ headers: {
35
+ accept: "application/#{@format}",
36
+ content_type: "application/#{@format}",
37
+ user_agent: user_agent
38
+ },
39
+ request: {
40
+ open_timeout: 10,
41
+ timeout: 30
42
+ }
43
+ }
44
+ end
45
+
46
+ def connection
47
+ @connection ||= Faraday.new(@endpoint, connection_options)
48
+ end
49
+
50
+ def middleware
51
+ @middleware ||= Faraday::RackBuilder.new do |faraday|
52
+ # Checks for files in the payload, otherwise leaves everything untouched
53
+ faraday.request :multipart
54
+ # Encodes as "application/x-www-form-urlencoded" if not already encoded
55
+ faraday.request :url_encoded
56
+ # Handle error responses
57
+ faraday.response :postcode_anywhere_raise_error
58
+ # Parse JSON response bodies
59
+ faraday.response :postcode_anywhere_parse_json
60
+ # Set default HTTP adapter
61
+ faraday.adapter :net_http
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def request(method, path, params = {}, body_hash = {})
68
+ attach_api_key_to params
69
+ connection.send(method.to_sym, path, params) do |request|
70
+ request.body = compile_body(body_hash) unless body_hash.empty?
71
+ end.env
72
+ rescue Faraday::Error::TimeoutError, Timeout::Error => error
73
+ raise(PostcodeAnywhere::Error::RequestTimeout.new(error))
74
+ rescue Faraday::Error::ClientError, JSON::ParserError => error
75
+ raise(PostcodeAnywhere::Error.new(error))
76
+ end
77
+
78
+ def attach_api_key_to(params)
79
+ params.merge!('Key' => @api_key) unless params.keys.include? 'Key'
80
+ end
81
+
82
+ def compile_body(body_hash)
83
+ body_hash.to_json
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,47 @@
1
+ module PostcodeAnywhere
2
+ module Configuration
3
+ VALID_CONNECTION_KEYS = [
4
+ :endpoint,
5
+ :api_key,
6
+ :method,
7
+ :user_agent
8
+ ].freeze
9
+
10
+ VALID_OPTIONS_KEYS = [:format].freeze
11
+ VALID_CONFIG_KEYS =
12
+ VALID_CONNECTION_KEYS +
13
+ VALID_OPTIONS_KEYS
14
+
15
+ DEFAULT_METHOD = :post
16
+ DEFAULT_USER_AGENT = "Postcode Anywhere Ruby Gem/#{PostcodeAnywhere::VERSION}"
17
+
18
+ DEFAULT_API_KEY = ''
19
+
20
+ DEFAULT_FORMAT = :json
21
+
22
+ DEFAULT_ENDPOINT = 'http://services.postcodeanywhere.co.uk/'
23
+
24
+ attr_accessor(*VALID_CONFIG_KEYS)
25
+
26
+ def self.extended(base)
27
+ base.reset
28
+ end
29
+
30
+ def reset
31
+ self.api_key = DEFAULT_API_KEY
32
+ self.endpoint = DEFAULT_ENDPOINT
33
+ self.format = DEFAULT_FORMAT
34
+ self.method = DEFAULT_METHOD
35
+ self.user_agent = DEFAULT_USER_AGENT
36
+ end
37
+
38
+ def configure
39
+ yield self
40
+ end
41
+
42
+ # Return the configuration values set in this module
43
+ def options
44
+ Hash[* VALID_CONFIG_KEYS.map { |key| [key, send(key)] }.flatten]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+ require 'postcode_anywhere/client'
2
+ require 'postcode_anywhere/email_validation/interactive'
3
+
4
+ module PostcodeAnywhere
5
+ module EmailValidation
6
+ class Client < ::PostcodeAnywhere::Client
7
+ include PostcodeAnywhere::EmailValidation::Interactive
8
+ def initialize(options = {})
9
+ super(options)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ require 'postcode_anywhere/utils'
2
+ require 'postcode_anywhere/email_validation/validation_result'
3
+
4
+ module PostcodeAnywhere
5
+ module EmailValidation
6
+ module Interactive
7
+ include ::PostcodeAnywhere::Utils
8
+
9
+ API_VERSION = '1.10'
10
+
11
+ VALIDATE_EMAIL_ENDPOINT =
12
+ "/EmailValidation/Interactive/Validate/v#{API_VERSION}/json3.ws"
13
+
14
+ def validate_email_address(email, timeout = 3, options = {})
15
+ options.merge!('Email' => email, 'Timeout' => timeout)
16
+ perform_with_object(
17
+ :get,
18
+ VALIDATE_EMAIL_ENDPOINT,
19
+ options,
20
+ PostcodeAnywhere::EmailValidation::ValidationResult
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ require 'postcode_anywhere/model_base'
2
+
3
+ module PostcodeAnywhere
4
+ module EmailValidation
5
+ class ValidationResult < PostcodeAnywhere::ModelBase
6
+ # The address the email was sent to.
7
+ # Example: info@google.com
8
+ attr_reader :email
9
+
10
+ # The mail server that was used to perform the server and account validation steps on.
11
+ # Not populated if the DNS record was not found.
12
+ # Example: google.com.s9a1.psmtp.com
13
+ attr_reader :mail_server
14
+
15
+ # Indicates that the format of the email appears valid.
16
+ # Example: true
17
+ attr_reader :valid_format
18
+
19
+ # Indicates that a valid DNS record was found for the email.
20
+ # Example: true
21
+ attr_reader :found_dns_record
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,118 @@
1
+ module PostcodeAnywhere
2
+ # Custom error class for rescuing from all PostcodeAnywhere errors
3
+ class Error < StandardError
4
+ attr_reader :code
5
+ attr_reader :cause
6
+ attr_reader :resolution
7
+
8
+ class << self
9
+ def from_response(error_hash)
10
+ message, code, cause, resolution = parse_error(error_hash)
11
+ new(message, code, cause, resolution)
12
+ end
13
+
14
+ def errors
15
+ @errors ||= {
16
+ 400 => PostcodeAnywhere::Error::BadRequest,
17
+ 401 => PostcodeAnywhere::Error::Unauthorized,
18
+ 403 => PostcodeAnywhere::Error::Forbidden,
19
+ 404 => PostcodeAnywhere::Error::NotFound,
20
+ 406 => PostcodeAnywhere::Error::NotAcceptable,
21
+ 408 => PostcodeAnywhere::Error::RequestTimeout,
22
+ 422 => PostcodeAnywhere::Error::UnprocessableEntity,
23
+ 429 => PostcodeAnywhere::Error::TooManyRequests,
24
+ 500 => PostcodeAnywhere::Error::InternalServerError,
25
+ 502 => PostcodeAnywhere::Error::BadGateway,
26
+ 503 => PostcodeAnywhere::Error::ServiceUnavailable,
27
+ 504 => PostcodeAnywhere::Error::GatewayTimeout
28
+ }
29
+ end
30
+
31
+ def postcode_anywhere_errors
32
+ @postcode_anywhere_errors ||= {
33
+ -1 => PostcodeAnywhere::Error::UnknownError,
34
+ 2 => PostcodeAnywhere::Error::UnknownKey,
35
+ 3 => PostcodeAnywhere::Error::AccountOutOfCredit,
36
+ 4 => PostcodeAnywhere::Error::IpDenied,
37
+ 5 => PostcodeAnywhere::Error::UrlDenied,
38
+ 6 => PostcodeAnywhere::Error::ServiceDeniedForKey,
39
+ 7 => PostcodeAnywhere::Error::ServiceDeniedForPlan,
40
+ 8 => PostcodeAnywhere::Error::KeyDailyLimitExceeded,
41
+ 9 => PostcodeAnywhere::Error::SurgeProtectorRunning,
42
+ 10 => PostcodeAnywhere::Error::SurgeProtectorTriggered,
43
+ 11 => PostcodeAnywhere::Error::NoValidLicense,
44
+ 12 => PostcodeAnywhere::Error::ManagementKeyRequired,
45
+ 13 => PostcodeAnywhere::Error::DemoLimitExceeded,
46
+ 14 => PostcodeAnywhere::Error::FreeLimitExceeded,
47
+ 15 => PostcodeAnywhere::Error::IncorrectKeyType,
48
+ 16 => PostcodeAnywhere::Error::KeyExpired,
49
+ 17 => PostcodeAnywhere::Error::KeyDailyLimitExceeded
50
+ }
51
+ end
52
+
53
+ private
54
+
55
+ def parse_error(error_hash)
56
+ if error_hash.nil?
57
+ ['', nil, '', '']
58
+ else
59
+ [
60
+ error_hash[:description],
61
+ error_hash[:error],
62
+ error_hash[:cause],
63
+ error_hash[:resolution]
64
+ ]
65
+ end
66
+ end
67
+
68
+ def extract_message_from_error(body)
69
+ m =
70
+ /The exception message is \'(.+)?\'\. See server logs for more details./.match(body)
71
+ return m.captures.first if m
72
+ end
73
+ end
74
+
75
+ def initialize(description = '', code = nil, cause = '', resolution = '')
76
+ super(description)
77
+ @code = code
78
+ @cause = cause
79
+ @resolution = resolution
80
+ end
81
+
82
+ ClientError = Class.new(self)
83
+ BadRequest = Class.new(ClientError)
84
+ Unauthorized = Class.new(ClientError)
85
+ Forbidden = Class.new(ClientError)
86
+ NotFound = Class.new(ClientError)
87
+ NotAcceptable = Class.new(ClientError)
88
+ RequestTimeout = Class.new(ClientError)
89
+ UnprocessableEntity = Class.new(ClientError)
90
+ TooManyRequests = Class.new(ClientError)
91
+ ServerError = Class.new(self)
92
+ InternalServerError = Class.new(ServerError)
93
+ BadGateway = Class.new(ServerError)
94
+ ServiceUnavailable = Class.new(ServerError)
95
+ GatewayTimeout = Class.new(ServerError)
96
+
97
+ # Postcode anywhere specific errors
98
+
99
+ UnknownError = Class.new(ServerError)
100
+ UnknownKey = Class.new(ClientError)
101
+ AccountOutOfCredit = Class.new(Forbidden)
102
+ IpDenied = Class.new(Forbidden)
103
+ UrlDenied = Class.new(Forbidden)
104
+ ServiceDeniedForKey = Class.new(Forbidden)
105
+ ServiceDeniedForPlan = Class.new(Forbidden)
106
+ KeyDailyLimitExceeded = Class.new(Forbidden)
107
+ SurgeProtectorRunning = Class.new(Forbidden)
108
+ SurgeProtectorTriggered = Class.new(Forbidden)
109
+ NoValidLicense = Class.new(Forbidden)
110
+ ManagementKeyRequired = Class.new(Forbidden)
111
+ DemoLimitExceeded = Class.new(Forbidden)
112
+ FreeLimitExceeded = Class.new(Forbidden)
113
+ IncorrectKeyType = Class.new(Forbidden)
114
+ KeyExpired = Class.new(Forbidden)
115
+
116
+ ServiceSpecificError = Class.new(ClientError)
117
+ end
118
+ end
@@ -0,0 +1,72 @@
1
+ require 'memoizable'
2
+
3
+ module PostcodeAnywhere
4
+ class ModelBase
5
+ include Memoizable
6
+ attr_reader :attrs
7
+ alias_method :to_h, :attrs
8
+ alias_method :to_hash, :to_h
9
+
10
+ class << self
11
+ def attr_reader(*attrs)
12
+ attrs.each do |attr|
13
+ define_attribute_method(attr)
14
+ define_predicate_method(attr)
15
+ end
16
+ end
17
+
18
+ def predicate_attr_reader(*attrs)
19
+ attrs.each do |attr|
20
+ define_predicate_method(attr)
21
+ deprecate_attribute_method(attr)
22
+ end
23
+ end
24
+
25
+ def object_attr_reader(klass, key1, key2 = nil)
26
+ define_attribute_method(key1, klass, key2)
27
+ define_predicate_method(key1)
28
+ end
29
+
30
+ def define_attribute_method(key1, klass = nil, key2 = nil)
31
+ define_method(key1) do ||
32
+ if @attrs[key1].nil? || @attrs[key1].respond_to?(:empty?) && @attrs[key1].empty?
33
+ # NullObject.new
34
+ else
35
+ if klass.nil?
36
+ @attrs[key1]
37
+ else
38
+ attrs = attrs_for_object(key1, key2)
39
+ PostcodeAnywhere.const_get(klass).new(attrs)
40
+ end
41
+ end
42
+ end
43
+ memoize(key1)
44
+ end
45
+
46
+ def define_predicate_method(key1, key2 = key1)
47
+ define_method(:"#{key1}?") do ||
48
+ !@attrs[key2].nil? &&
49
+ @attrs[key2] != false &&
50
+ !(@attrs[key2].respond_to?(:empty?) &&
51
+ @attrs[key2].empty?)
52
+ end
53
+ memoize(:"#{key1}?")
54
+ end
55
+ end
56
+
57
+ def initialize(attrs = {})
58
+ @attrs = attrs || {}
59
+ end
60
+
61
+ private
62
+
63
+ def attrs_for_object(key1, key2 = nil)
64
+ if key2.nil?
65
+ @attrs[key1]
66
+ else
67
+ attrs = @attrs.dup
68
+ attrs.delete(key1).merge(key2 => attrs)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ module PostcodeAnywhere
2
+ class Request
3
+ attr_accessor :client, :request_method, :path, :body, :options
4
+ alias_method :verb, :request_method
5
+
6
+ def initialize(client, request_method, path, body_hash = {}, options = {})
7
+ @client = client
8
+ @request_method = request_method.to_sym
9
+ @path = path
10
+ @body_hash = body_hash
11
+ @options = options
12
+ end
13
+
14
+ def perform
15
+ @client.send(@request_method, @path, @body_hash, @options).body
16
+ end
17
+
18
+ def perform_with_object(klass)
19
+ result = perform
20
+ if result.class == Array
21
+ klass.new(result.first)
22
+ else
23
+ klass.new(result)
24
+ end
25
+ end
26
+
27
+ def perform_with_objects(klass)
28
+ perform.map do |element|
29
+ klass.new(element)
30
+ end
31
+ end
32
+ end
33
+ end