geocodable 0.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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +103 -0
- data/Rakefile +2 -0
- data/geocodable.gemspec +29 -0
- data/lib/certs/ca-certificates.crt +3918 -0
- data/lib/geocodable.rb +21 -0
- data/lib/geocodable/geocodable_error.rb +24 -0
- data/lib/geocodable/json_parser.rb +20 -0
- data/lib/geocodable/request.rb +55 -0
- data/lib/geocodable/version.rb +3 -0
- data/spec/geocode_spec.rb +104 -0
- data/spec/spec_helper.rb +7 -0
- metadata +158 -0
data/lib/geocodable.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'excon'
|
|
3
|
+
require 'faraday'
|
|
4
|
+
|
|
5
|
+
require 'geocodable/version'
|
|
6
|
+
require 'geocodable/geocodable_error'
|
|
7
|
+
require 'geocodable/json_parser'
|
|
8
|
+
require 'geocodable/request'
|
|
9
|
+
|
|
10
|
+
module Geocodable
|
|
11
|
+
@api_base = 'https://api.geocodable.io/'
|
|
12
|
+
@ssl_verify_certs = true
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
attr_accessor :api_key, :api_base, :ssl_verify_certs
|
|
16
|
+
|
|
17
|
+
def geocode(query)
|
|
18
|
+
Request.get('/v1/geocode.json', { query: query })
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Geocodable
|
|
2
|
+
class GeocodableError < StandardError
|
|
3
|
+
attr_reader :message
|
|
4
|
+
attr_reader :http_status
|
|
5
|
+
attr_reader :http_body
|
|
6
|
+
|
|
7
|
+
def initialize(message=nil, http_status=nil, http_body=nil)
|
|
8
|
+
@message = message
|
|
9
|
+
@http_status = http_status
|
|
10
|
+
@http_body = http_body
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
str = @http_status.nil? ? '' : "HTTP Status #{@http_status} "
|
|
15
|
+
"#{str}#{@message}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class InvalidRequestError < GeocodableError; end
|
|
20
|
+
class AuthenticationError < GeocodableError; end
|
|
21
|
+
class AccessDisabledError < GeocodableError; end
|
|
22
|
+
class OverRequestLimitError < GeocodableError; end
|
|
23
|
+
class APIError < GeocodableError; end
|
|
24
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Geocodable
|
|
2
|
+
class Request
|
|
3
|
+
class JSONParser < Faraday::Response::Middleware
|
|
4
|
+
def on_complete(env)
|
|
5
|
+
begin
|
|
6
|
+
env.body = JSON.parse(env.body, symbolize_names: true) if is_json?(env)
|
|
7
|
+
rescue JSON::ParserError
|
|
8
|
+
raise APIError.new(
|
|
9
|
+
"Invalid response object from API: #{env.body}", env.status, env.body)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def is_json?(env)
|
|
16
|
+
env[:response_headers]['Content-Type'].match 'application/json'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Geocodable
|
|
2
|
+
class Request
|
|
3
|
+
@ssl_bundle_path = File.dirname(__FILE__) + '/../certs/ca-certificates.crt'
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
def get(url, params={})
|
|
7
|
+
response = conn.get url, params, headers
|
|
8
|
+
handle_api_error(response) unless response.success?
|
|
9
|
+
response.body
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def headers
|
|
13
|
+
hash = { user_agent: "geocodable-ruby/#{Geocodable::VERSION}" }
|
|
14
|
+
hash[:authorization] = "Bearer #{api_key}" unless api_key.nil?
|
|
15
|
+
hash
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def conn
|
|
19
|
+
Faraday::Response.register_middleware json_parser: JSONParser
|
|
20
|
+
conn = Faraday.new(Geocodable.api_base, ssl: ssl) do |faraday|
|
|
21
|
+
faraday.adapter :excon
|
|
22
|
+
faraday.response :json_parser
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ssl
|
|
27
|
+
{ verify: Geocodable.ssl_verify_certs, ca_file: @ssl_bundle_path }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def api_key
|
|
31
|
+
Geocodable.api_key
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def handle_api_error(response)
|
|
35
|
+
code, body = response.status, response.body
|
|
36
|
+
begin
|
|
37
|
+
error = body[:errors] || body[:error]
|
|
38
|
+
error = error.first if error.kind_of? Array
|
|
39
|
+
rescue
|
|
40
|
+
raise APIError.new(
|
|
41
|
+
"Invalid response object from API: #{body.inspect}", code, body)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
case code
|
|
45
|
+
when 400, 404 then raise InvalidRequestError.new(error, code, body)
|
|
46
|
+
when 401 then raise AuthenticationError.new(error, code, body)
|
|
47
|
+
when 403 then raise AccessDisabledError.new(error, code, body)
|
|
48
|
+
when 429 then raise OverRequestLimitError.new(error, code, body)
|
|
49
|
+
else
|
|
50
|
+
raise APIError.new(error, code, body)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Geocodable do
|
|
4
|
+
describe '#api_key=' do
|
|
5
|
+
it 'sets the API key' do
|
|
6
|
+
Geocodable.api_key = 'abc123'
|
|
7
|
+
expect(Geocodable.api_key).to eq('abc123')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe '#geocode' do
|
|
12
|
+
before { Geocodable.api_key = 'abc' }
|
|
13
|
+
|
|
14
|
+
it 'returns results' do
|
|
15
|
+
stub_api_request(body: '{"found": 1}')
|
|
16
|
+
resp = Geocodable.geocode('London')
|
|
17
|
+
expect(resp[:found]).to eq(1)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'raises InvalidRequestError with missing query param' do
|
|
21
|
+
stub_api_request(params: { query: '' },
|
|
22
|
+
status: 400,
|
|
23
|
+
body: '{"errors": ["Query parameter missing"]}')
|
|
24
|
+
expect {
|
|
25
|
+
Geocodable.geocode('')
|
|
26
|
+
}.to raise_error(Geocodable::InvalidRequestError) { |e|
|
|
27
|
+
expect(e.message).to eq('Query parameter missing')
|
|
28
|
+
expect(e.http_status).to eq(400)
|
|
29
|
+
expect(e.http_body).to eq(errors: ['Query parameter missing'])
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'raises AuthenticationError with invalid API key' do
|
|
34
|
+
stub_api_request(headers: {},
|
|
35
|
+
status: 401,
|
|
36
|
+
body: '{"errors": ["Unauthorized API key"]}')
|
|
37
|
+
Geocodable.api_key = nil
|
|
38
|
+
expect {
|
|
39
|
+
Geocodable.geocode('London')
|
|
40
|
+
}.to raise_error(Geocodable::AuthenticationError) { |e|
|
|
41
|
+
expect(e.message).to eq('Unauthorized API key')
|
|
42
|
+
expect(e.http_status).to eq(401)
|
|
43
|
+
expect(e.http_body).to eq(errors: ['Unauthorized API key'])
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'raises AccessDisabledError when api_key disabled' do
|
|
48
|
+
stub_api_request(status: 403,
|
|
49
|
+
body: '{"errors": ["API key disabled by owner"]}')
|
|
50
|
+
expect {
|
|
51
|
+
Geocodable.geocode('London')
|
|
52
|
+
}.to raise_error(Geocodable::AccessDisabledError) { |e|
|
|
53
|
+
expect(e.message).to eq('API key disabled by owner')
|
|
54
|
+
expect(e.http_status).to eq(403)
|
|
55
|
+
expect(e.http_body).to eq(errors: ['API key disabled by owner'])
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'raises OverRequestLimitError when api_key disabled' do
|
|
60
|
+
stub_api_request(status: 429, body: '{"errors": ["Over request limit"]}')
|
|
61
|
+
expect {
|
|
62
|
+
Geocodable.geocode('London')
|
|
63
|
+
}.to raise_error(Geocodable::OverRequestLimitError) { |e|
|
|
64
|
+
expect(e.message).to eq('Over request limit')
|
|
65
|
+
expect(e.http_status).to eq(429)
|
|
66
|
+
expect(e.http_body).to eq(errors: ['Over request limit'])
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'raises APIError when 500 server error' do
|
|
71
|
+
stub_api_request(status: 500,
|
|
72
|
+
body: '{"status":"500", "error":"Internal server error"}')
|
|
73
|
+
expect {
|
|
74
|
+
Geocodable.geocode('London')
|
|
75
|
+
}.to raise_error(Geocodable::APIError) { |e|
|
|
76
|
+
expect(e.message).to eq('Internal server error')
|
|
77
|
+
expect(e.http_status).to eq(500)
|
|
78
|
+
expect(e.http_body).to eq(error: 'Internal server error', status: '500')
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def stub_api_request(args)
|
|
86
|
+
args = {
|
|
87
|
+
headers: { authorization: 'Bearer abc' },
|
|
88
|
+
params: { query: 'London' },
|
|
89
|
+
status: 200,
|
|
90
|
+
body: ''
|
|
91
|
+
}.update(args)
|
|
92
|
+
query = URI.encode_www_form(args[:params])
|
|
93
|
+
args[:headers].update(
|
|
94
|
+
'Host' => 'api.geocodable.io:443',
|
|
95
|
+
'User-Agent' => "geocodable-ruby/#{Geocodable::VERSION}"
|
|
96
|
+
)
|
|
97
|
+
stub_request(:get, "https://api.geocodable.io/v1/geocode.json?#{query}").
|
|
98
|
+
with(headers: args[:headers]).
|
|
99
|
+
to_return(
|
|
100
|
+
body: args[:body],
|
|
101
|
+
status: args[:status],
|
|
102
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
103
|
+
)
|
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: geocodable
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sam Levy
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2014-04-26 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.6'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.6'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: webmock
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: faraday
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 0.9.0
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 0.9.0
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: excon
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 0.32.1
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 0.32.1
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: json
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: 1.8.1
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: 1.8.1
|
|
111
|
+
description: Geocodable is a UK geocoding service using Open Data
|
|
112
|
+
email:
|
|
113
|
+
- sam@geocodable.io
|
|
114
|
+
executables: []
|
|
115
|
+
extensions: []
|
|
116
|
+
extra_rdoc_files: []
|
|
117
|
+
files:
|
|
118
|
+
- ".gitignore"
|
|
119
|
+
- Gemfile
|
|
120
|
+
- LICENSE.txt
|
|
121
|
+
- README.md
|
|
122
|
+
- Rakefile
|
|
123
|
+
- geocodable.gemspec
|
|
124
|
+
- lib/certs/ca-certificates.crt
|
|
125
|
+
- lib/geocodable.rb
|
|
126
|
+
- lib/geocodable/geocodable_error.rb
|
|
127
|
+
- lib/geocodable/json_parser.rb
|
|
128
|
+
- lib/geocodable/request.rb
|
|
129
|
+
- lib/geocodable/version.rb
|
|
130
|
+
- spec/geocode_spec.rb
|
|
131
|
+
- spec/spec_helper.rb
|
|
132
|
+
homepage: http://geocodable.io/docs
|
|
133
|
+
licenses:
|
|
134
|
+
- MIT
|
|
135
|
+
metadata: {}
|
|
136
|
+
post_install_message:
|
|
137
|
+
rdoc_options: []
|
|
138
|
+
require_paths:
|
|
139
|
+
- lib
|
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
146
|
+
requirements:
|
|
147
|
+
- - ">="
|
|
148
|
+
- !ruby/object:Gem::Version
|
|
149
|
+
version: '0'
|
|
150
|
+
requirements: []
|
|
151
|
+
rubyforge_project:
|
|
152
|
+
rubygems_version: 2.2.2
|
|
153
|
+
signing_key:
|
|
154
|
+
specification_version: 4
|
|
155
|
+
summary: UK geocoding API wrapper for Geocodable
|
|
156
|
+
test_files:
|
|
157
|
+
- spec/geocode_spec.rb
|
|
158
|
+
- spec/spec_helper.rb
|