firstclasspostcodes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/.dependabot/config.yml +12 -0
  3. data/.github/workflows/gem.yml +53 -0
  4. data/.gitignore +9 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +78 -0
  7. data/Gemfile +25 -0
  8. data/LICENSE +21 -0
  9. data/Rakefile +11 -0
  10. data/firstclasspostcodes.gemspec +54 -0
  11. data/lib/firstclasspostcodes.rb +33 -0
  12. data/lib/firstclasspostcodes/client.rb +81 -0
  13. data/lib/firstclasspostcodes/configuration.rb +156 -0
  14. data/lib/firstclasspostcodes/events.rb +27 -0
  15. data/lib/firstclasspostcodes/operations.rb +4 -0
  16. data/lib/firstclasspostcodes/operations/get_lookup.rb +52 -0
  17. data/lib/firstclasspostcodes/operations/get_postcode.rb +40 -0
  18. data/lib/firstclasspostcodes/operations/methods/format_address.rb +38 -0
  19. data/lib/firstclasspostcodes/operations/methods/list_addresses.rb +29 -0
  20. data/lib/firstclasspostcodes/response_error.rb +29 -0
  21. data/lib/firstclasspostcodes/version.rb +5 -0
  22. data/spec/firstclasspostcodes/client_spec.rb +94 -0
  23. data/spec/firstclasspostcodes/events_spec.rb +41 -0
  24. data/spec/firstclasspostcodes/operations/get_lookup_spec.rb +103 -0
  25. data/spec/firstclasspostcodes/operations/get_postcode_spec.rb +58 -0
  26. data/spec/firstclasspostcodes/operations/methods/format_address_spec.rb +106 -0
  27. data/spec/firstclasspostcodes/operations/methods/list_addresses_spec.rb +75 -0
  28. data/spec/firstclasspostcodes/response_error_spec.rb +43 -0
  29. data/spec/firstclasspostcodes/version_spec.rb +9 -0
  30. data/spec/firstclasspostcodes_spec.rb +108 -0
  31. data/spec/spec_helper.rb +42 -0
  32. data/spec/support/events_examples.rb +11 -0
  33. metadata +123 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4e926c2b2819dd25138a2df749f49930a486e220d9846766855a9b31dba92d8b
4
+ data.tar.gz: 3094812034c5a12258c3d8dccc87c0add4d9e173b4011657ac4c7dab564e314e
5
+ SHA512:
6
+ metadata.gz: 2ee17eead9837df323e51afcaa91c19b7d9263d59349c4364493ac0fd85f400c3a199cc041582834488e11324afa8946920c2023fa08c4332b153dc23293c39d
7
+ data.tar.gz: 48310e7b50ca7a3de522c393f835e678706e31a4c6ae14f1eda6c7ba5686a1099b18eb4bc2d94998bae8bc2a3c0605aa5de6e04d15636eb302a162c411175a29
@@ -0,0 +1,12 @@
1
+ version: 1
2
+
3
+ update_configs:
4
+
5
+ - package_manager: "ruby:bundler"
6
+ directory: "/"
7
+ update_schedule: "weekly"
8
+ version_requirement_updates: increase_versions
9
+ automerged_updates:
10
+ - match:
11
+ dependency_type: "all"
12
+ update_type: "all"
@@ -0,0 +1,53 @@
1
+ name: "Build, test and release"
2
+
3
+ on:
4
+ - push
5
+ - pull_request
6
+
7
+ jobs:
8
+ build:
9
+ name: Build + Publish
10
+ runs-on: ubuntu-latest
11
+
12
+ services:
13
+ mock-api:
14
+ image: firstclasspostcodes/mock:latest
15
+ ports:
16
+ - '80:3000'
17
+
18
+ steps:
19
+ - uses: actions/checkout@master
20
+ - name: Set up Ruby 2.6
21
+ uses: actions/setup-ruby@v1
22
+ with:
23
+ version: 2.6.x
24
+ - run: gem install bundler
25
+ - run: bundle install --jobs 4 --retry 3
26
+ - name: Rake
27
+ run: bundle exec rake
28
+ env:
29
+ API_URL: http://localhost:3000
30
+ API_KEY: '111111111111'
31
+ - name: Publish to GPR
32
+ if: github.ref == 'refs/heads/master'
33
+ run: |
34
+ mkdir -p $HOME/.gem
35
+ touch $HOME/.gem/credentials
36
+ chmod 0600 $HOME/.gem/credentials
37
+ printf -- "---\n:github: Bearer ${GITHUB_TOKEN}\n" > $HOME/.gem/credentials
38
+ gem build *.gemspec
39
+ gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
40
+ env:
41
+ OWNER: firstclasspostcodes
42
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
43
+ - name: Publish to RubyGems
44
+ if: github.ref == 'refs/heads/master'
45
+ run: |
46
+ mkdir -p $HOME/.gem
47
+ touch $HOME/.gem/credentials
48
+ chmod 0600 $HOME/.gem/credentials
49
+ printf -- "---\n:rubygems_api_key: ${RUBYGEMS_AUTH_TOKEN}\n" > $HOME/.gem/credentials
50
+ gem build *.gemspec
51
+ gem push *.gem
52
+ env:
53
+ RUBYGEMS_AUTH_TOKEN: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
@@ -0,0 +1,9 @@
1
+ /firstclasspostcodes-*.gem
2
+ /Gemfile.lock
3
+ .rvmrc
4
+ Gemfile.lock
5
+ tags
6
+ vendor
7
+ /.bundle/
8
+ coverage/
9
+ .idea/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,78 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+ TargetRubyVersion: 2.3
4
+
5
+ Exclude:
6
+ # brandur: Exclude ephmeral script-like files that I use to try and
7
+ # reproduce problems with the library. If you know of a better way of doing
8
+ # this (e.g. exclude files not tracked by Git), feel free to change it.
9
+ - "test_*"
10
+
11
+ Layout/CaseIndentation:
12
+ EnforcedStyle: end
13
+
14
+ Layout/FirstArrayElementIndentation:
15
+ EnforcedStyle: consistent
16
+
17
+ Layout/FirstHashElementIndentation:
18
+ EnforcedStyle: consistent
19
+
20
+ # This can be re-enabled once we're 2.3+ only and can use the squiggly heredoc
21
+ # operator. Prior to that, Rubocop recommended bringing in a library like
22
+ # ActiveSupport to get heredoc indentation, which is just terrible.
23
+ Layout/HeredocIndentation:
24
+ Enabled: false
25
+
26
+ Layout/LineLength:
27
+ Max: 150
28
+ Exclude:
29
+ - "spec/**/*.rb"
30
+
31
+ Metrics/BlockLength:
32
+ Max: 40
33
+ Exclude:
34
+ # `context` in tests are blocks and get quite large, so exclude the test
35
+ # directory from having to adhere to this rule.
36
+ - "spec/**/*.rb"
37
+
38
+ Metrics/ClassLength:
39
+ Exclude:
40
+ # Test classes get quite large, so exclude the test directory from having
41
+ # to adhere to this rule.
42
+ - "test/**/*.rb"
43
+
44
+ Metrics/PerceivedComplexity:
45
+ Enabled: false
46
+
47
+ Metrics/CyclomaticComplexity:
48
+ Enabled: false
49
+
50
+ Metrics/MethodLength:
51
+ Enabled: false
52
+
53
+ Metrics/AbcSize:
54
+ Max: 50
55
+
56
+ Metrics/ModuleLength:
57
+ Enabled: false
58
+
59
+ Style/Documentation:
60
+ Enabled: false
61
+
62
+ Style/AccessModifierDeclarations:
63
+ EnforcedStyle: inline
64
+
65
+ Style/FrozenStringLiteralComment:
66
+ EnforcedStyle: always
67
+
68
+ Style/NumericPredicate:
69
+ Enabled: false
70
+
71
+ Style/StringLiterals:
72
+ EnforcedStyle: double_quotes
73
+
74
+ Style/TrailingCommaInArrayLiteral:
75
+ EnforcedStyleForMultiline: consistent_comma
76
+
77
+ Style/TrailingCommaInHashLiteral:
78
+ EnforcedStyleForMultiline: consistent_comma
data/Gemfile ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem "codecov", require: false
9
+ gem "coveralls", require: false
10
+ gem "rake"
11
+ gem "rspec"
12
+
13
+ # Rubocop changes pretty quickly: new cops get added and old cops change
14
+ # names or go into new namespaces. This is a library and we don't have
15
+ # `Gemfile.lock` checked in, so to prevent good builds from suddenly going
16
+ # bad, pin to a specific version number here. Try to keep this relatively
17
+ # up-to-date, but it's not the end of the world if it's not.
18
+ gem "rubocop", "0.79", require: false
19
+
20
+ platforms :mri do
21
+ gem "byebug"
22
+ gem "pry"
23
+ gem "pry-byebug"
24
+ end
25
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2018-2020 Firstclasspostcodes (https://firstclasspostcodes.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+ require "rspec/core/rake_task"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: %i[spec rubocop]
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(::File.join(::File.dirname(__FILE__), "lib"))
4
+
5
+ require "firstclasspostcodes/version"
6
+
7
+ REPO_URL = "github.com/firstclasspostcodes/firstclasspostcodes-ruby"
8
+
9
+ DOMAIN_NAME = "firstclasspostcodes.com"
10
+
11
+ Gem::Specification.new do |s|
12
+ s.name = "firstclasspostcodes"
13
+
14
+ s.version = Firstclasspostcodes::VERSION
15
+
16
+ s.required_ruby_version = ">= 2.3.0"
17
+
18
+ s.summary = <<-SUMMARY
19
+ Ruby bindings for the Firstclasspostcodes API
20
+ SUMMARY
21
+
22
+ s.description = <<-DESC
23
+ With 500 requests for free per month, get started with the
24
+ fastest and cheapest address lookup service in the UK.
25
+
26
+ See https://#{DOMAIN_NAME} for more details.
27
+ DESC
28
+
29
+ s.author = "Firstclasspostcodes"
30
+
31
+ s.email = "support@#{DOMAIN_NAME}"
32
+
33
+ s.homepage = "https://docs.#{DOMAIN_NAME}"
34
+
35
+ s.license = "MIT"
36
+
37
+ s.metadata = {
38
+ "bug_tracker_uri" => "https://#{REPO_URL}/issues",
39
+ "changelog_uri" => "https://#{REPO_URL}/blob/master/CHANGELOG.md",
40
+ "documentation_uri" => "https://docs.#{DOMAIN_NAME}",
41
+ "github_repo" => "ssh://#{REPO_URL}",
42
+ "homepage_uri" => "https://#{DOMAIN_NAME}",
43
+ "source_code_uri" => "https://#{REPO_URL}",
44
+ }
45
+
46
+ s.files = `git ls-files`.split("\n")
47
+ s.test_files = `git ls-files -- test/*`.split("\n")
48
+ s.executables = `git ls-files -- bin/*`.split("\n")
49
+ .map { |f| ::File.basename(f) }
50
+ s.require_paths = ["lib"]
51
+
52
+ s.add_runtime_dependency "json", "~> 2.2", ">= 2.2.0"
53
+ s.add_runtime_dependency "typhoeus", "~> 1.3", ">= 1.3.1"
54
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "firstclasspostcodes/client"
4
+ require "firstclasspostcodes/configuration"
5
+ require "firstclasspostcodes/events"
6
+ require "firstclasspostcodes/operations"
7
+ require "firstclasspostcodes/version"
8
+
9
+ module Firstclasspostcodes
10
+ class << self
11
+ # Customize default settings for the SDK using block.
12
+ # Firstclasspostcodes.configure do |config|
13
+ # config.api_key = "xxx"
14
+ # end
15
+ # If no block given, return the default Configuration object.
16
+ def configure
17
+ if block_given?
18
+ yield(Configuration.default)
19
+ else
20
+ Configuration.default
21
+ end
22
+ end
23
+ end
24
+
25
+ class Client
26
+ # Utility module mixins
27
+ include Events
28
+
29
+ # Include all API operations below
30
+ include Operations::GetPostcode
31
+ include Operations::GetLookup
32
+ end
33
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "typhoeus"
5
+
6
+ require "firstclasspostcodes/response_error"
7
+
8
+ module Firstclasspostcodes
9
+ class Client
10
+ # The Configuration object holding settings to be used in the API client.
11
+ attr_accessor :config
12
+
13
+ # The User-agent for the library
14
+ attr_reader :user_agent
15
+
16
+ # @option config [Configuration] Configuration for initializing the object
17
+ def initialize(config = Configuration.default)
18
+ @config = config
19
+ @user_agent = "Firstclasspostcodes/ruby@#{Firstclasspostcodes::VERSION}"
20
+ on("request") { |req| @config.logger.debug(req) if @config.debug }
21
+ on("response") { |req| @config.logger.debug(req) if @config.debug }
22
+ on("error") { |req| @config.logger.error(req) }
23
+ end
24
+
25
+ def request(opts)
26
+ url = build_request_url(opts[:path])
27
+
28
+ request_params = {
29
+ params: opts[:query_params] || {},
30
+ method: opts[:method].to_sym.downcase,
31
+ }
32
+
33
+ request_params.merge!(@config.to_request_params)
34
+
35
+ emit("request", request_params)
36
+
37
+ response = call_request(url, request_params)
38
+
39
+ data = JSON.parse(response.body, symbolize_names: true)
40
+
41
+ emit("response", data)
42
+
43
+ data
44
+ rescue ResponseError => e
45
+ emit("error", e)
46
+ raise e
47
+ end
48
+
49
+ def call_request(*args)
50
+ response = Typhoeus::Request.new(*args).run
51
+
52
+ return response if response.success?
53
+
54
+ raise handle_request_error(response)
55
+ end
56
+
57
+ def handle_request_error(response)
58
+ raise ResponseError.new("Connection timed out", "timeout") if response.timed_out?
59
+
60
+ raise ResponseError.new(response.return_message, "liberror") if response.code == 0
61
+
62
+ error = begin
63
+ JSON.parse(response.body, symbolize_names: true)
64
+ rescue JSON::ParserError
65
+ response.body
66
+ end
67
+
68
+ raise ResponseError.new(error, "network-error")
69
+ end
70
+
71
+ def build_request_url(path)
72
+ # Add leading and trailing slashes to path
73
+ path = "/#{path}".gsub(%r{/+}, "/")
74
+ @config.base_url + path
75
+ end
76
+
77
+ def self.default
78
+ @default ||= Client.new
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Firstclasspostcodes
4
+ class Configuration
5
+ # Defines API keys used with API Key authentications.
6
+ #
7
+ # @return [String] the value of the API key being used
8
+ #
9
+ # @example parameter name is "api_key", API key is "xxx"
10
+ # config.api_key['api_key'] = 'xxx'
11
+ attr_accessor :api_key
12
+
13
+ # Defines url host
14
+ attr_reader :host
15
+
16
+ # Defines the content type requested and returned
17
+ attr_reader :content
18
+
19
+ # Defines HTTP protocol to be used
20
+ attr_reader :protocol
21
+
22
+ # Defines url base path
23
+ attr_reader :base_path
24
+
25
+ # Defines the logger used for debugging.
26
+ # Default to `Rails.logger` (when in Rails) or logging to STDOUT.
27
+ #
28
+ # @return [#debug]
29
+ attr_accessor :logger
30
+
31
+ # Set this to enable/disable debugging. When enabled (set to true), HTTP request/response
32
+ # details will be logged with `logger.debug` (see the `logger` attribute).
33
+ # Default to false.
34
+ #
35
+ # @return [true, false]
36
+ attr_accessor :debug
37
+
38
+ # The time limit for HTTP request in seconds.
39
+ # Default to 0 (never times out).
40
+ attr_accessor :timeout
41
+
42
+ ### TLS/SSL setting
43
+ # Set this to false to skip verifying SSL certificate when calling API from https server.
44
+ # Default to true.
45
+ #
46
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
47
+ #
48
+ # @return [true, false]
49
+ attr_accessor :verify_ssl
50
+
51
+ ### TLS/SSL setting
52
+ # Set this to false to skip verifying SSL host name
53
+ # Default to true.
54
+ #
55
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
56
+ #
57
+ # @return [true, false]
58
+ attr_accessor :verify_ssl_host
59
+
60
+ ### TLS/SSL setting
61
+ # Set this to customize the certificate file to verify the peer.
62
+ #
63
+ # @return [String] the path to the certificate file
64
+ #
65
+ # @see The `cainfo` option of Typhoeus, `--cert` option of libcurl. Related source code:
66
+ # https://github.com/typhoeus/typhoeus/blob/master/lib/typhoeus/easy_factory.rb#L145
67
+ attr_accessor :ssl_ca_cert
68
+
69
+ ### TLS/SSL setting
70
+ # Client certificate file (for client certificate)
71
+ attr_accessor :cert_file
72
+
73
+ ### TLS/SSL setting
74
+ # Client private key file (for client certificate)
75
+ attr_accessor :key_file
76
+
77
+ def initialize
78
+ @api_key = nil
79
+ @debug = false
80
+ @host = "api.firstclasspostcodes.com"
81
+ @content = "json"
82
+ @protocol = "https"
83
+ @base_path = "/data"
84
+ @timeout = 0
85
+ @verify_ssl = true
86
+ @verify_ssl_host = true
87
+ @cert_file = nil
88
+ @key_file = nil
89
+ @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
90
+ yield(self) if block_given?
91
+ end
92
+
93
+ # The default Configuration object.
94
+ def self.default
95
+ @default ||= Configuration.new
96
+ end
97
+
98
+ def configure
99
+ yield(self) if block_given?
100
+ end
101
+
102
+ def debug?
103
+ debug
104
+ end
105
+
106
+ def geo_json?
107
+ content == "geo+json"
108
+ end
109
+
110
+ def content=(content)
111
+ raise StandardError, `"#{content}" is not a valid content-type` unless %w[json geo+json].include?(content)
112
+
113
+ @content = content
114
+ end
115
+
116
+ def protocol=(protocol)
117
+ # remove :// from protocol
118
+ @protocol = protocol.sub(%r{://}, "")
119
+ end
120
+
121
+ def host=(host)
122
+ # remove http(s):// and anything after a slash
123
+ @host = host.sub(%r{https?://}, "").split("/").first
124
+ end
125
+
126
+ def base_path=(base_path)
127
+ # Add leading and trailing slashes to base_path
128
+ @base_path = "/#{base_path}".gsub(%r{/+}, "/")
129
+ @base_path = "" if @base_path == "/"
130
+ end
131
+
132
+ def base_url
133
+ path = [host, base_path].join("/").gsub(%r{/+}, "/")
134
+ "#{protocol}://#{path}".sub(%r{/+\z}, "")
135
+ end
136
+
137
+ def to_request_params
138
+ params = {
139
+ headers: {
140
+ 'x-api-key': api_key,
141
+ accept: "application/#{content}; q=1.0, application/json; q=0.5",
142
+ },
143
+ timeout: timeout,
144
+ ssl_verifypeer: verify_ssl,
145
+ ssl_verifyhost: verify_ssl_host ? 2 : 0,
146
+ sslcert: cert_file,
147
+ sslkey: key_file,
148
+ verbose: debug,
149
+ }
150
+
151
+ params[:cainfo] = ssl_ca_cert if ssl_ca_cert
152
+
153
+ params
154
+ end
155
+ end
156
+ end