elastomer-client 0.3.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 +8 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +108 -0
- data/Rakefile +9 -0
- data/docs/notifications.md +71 -0
- data/elastomer-client.gemspec +30 -0
- data/lib/elastomer/client.rb +307 -0
- data/lib/elastomer/client/bulk.rb +257 -0
- data/lib/elastomer/client/cluster.rb +208 -0
- data/lib/elastomer/client/docs.rb +432 -0
- data/lib/elastomer/client/errors.rb +51 -0
- data/lib/elastomer/client/index.rb +407 -0
- data/lib/elastomer/client/multi_search.rb +115 -0
- data/lib/elastomer/client/nodes.rb +87 -0
- data/lib/elastomer/client/scan.rb +161 -0
- data/lib/elastomer/client/template.rb +85 -0
- data/lib/elastomer/client/warmer.rb +96 -0
- data/lib/elastomer/core_ext/time.rb +7 -0
- data/lib/elastomer/middleware/encode_json.rb +51 -0
- data/lib/elastomer/middleware/opaque_id.rb +69 -0
- data/lib/elastomer/middleware/parse_json.rb +39 -0
- data/lib/elastomer/notifications.rb +83 -0
- data/lib/elastomer/version.rb +7 -0
- data/script/bootstrap +16 -0
- data/script/cibuild +28 -0
- data/script/console +9 -0
- data/script/testsuite +10 -0
- data/test/assertions.rb +74 -0
- data/test/client/bulk_test.rb +226 -0
- data/test/client/cluster_test.rb +113 -0
- data/test/client/docs_test.rb +394 -0
- data/test/client/index_test.rb +244 -0
- data/test/client/multi_search_test.rb +129 -0
- data/test/client/nodes_test.rb +35 -0
- data/test/client/scan_test.rb +84 -0
- data/test/client/stubbed_client_tests.rb +40 -0
- data/test/client/template_test.rb +33 -0
- data/test/client/warmer_test.rb +56 -0
- data/test/client_test.rb +86 -0
- data/test/core_ext/time_test.rb +46 -0
- data/test/middleware/encode_json_test.rb +53 -0
- data/test/middleware/opaque_id_test.rb +39 -0
- data/test/middleware/parse_json_test.rb +54 -0
- data/test/test_helper.rb +94 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6cd17dd15348e159466cead916479b5bcf68b80b
|
4
|
+
data.tar.gz: b2f0b0e2455ad13ed6a3a1670cf20a58e6239501
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c9c2a2798145fa90c3291459de33dcdf01c1ad806b64e19b719fb6f5679c3b4b165583cb3ee4101a1f3e17879d15da93c350e88703256c1fab9755df80eda187
|
7
|
+
data.tar.gz: ce53c9838c420f6932d7792a088bdcd6ea7d3735e12f3df51d52459cc849b934cc91ff0703317574ad01ab75d0ca91ad0ef839d55cf7988a7417b499e52fcaed
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.0-github
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 GitHub Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# Elastomer
|
2
|
+
|
3
|
+
Making a stupid simple ElasticSearch client so your project can be smarter!
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
Use [Boxen](https://setup.githubapp.com) to get your machine set up. Then:
|
8
|
+
|
9
|
+
```
|
10
|
+
$ git clone https://github.com/github/elastomer-client.git
|
11
|
+
$ cd elastomer-client
|
12
|
+
$ script/bootstrap
|
13
|
+
$ script/testsuite
|
14
|
+
```
|
15
|
+
|
16
|
+
## Client
|
17
|
+
|
18
|
+
The client provides a one-to-one mapping to the ElasticSearch [API
|
19
|
+
endpoints](http://www.elasticsearch.org/guide/reference/api/). The API is
|
20
|
+
decomposed into logical sections and accessed according to what you are trying
|
21
|
+
to accomplish. Each logical section is represented as a [client
|
22
|
+
class](lib/elastomer/client) and a top-level accessor is provided for each.
|
23
|
+
|
24
|
+
#### Cluster
|
25
|
+
|
26
|
+
API endpoints dealing with cluster level information and settings are found in
|
27
|
+
the [Cluster](lib/elastomer/client/cluster.rb) class.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'elastomer/client'
|
31
|
+
client = Elastomer::Client.new
|
32
|
+
|
33
|
+
# the current health summary
|
34
|
+
client.cluster.health
|
35
|
+
|
36
|
+
# detailed cluster state information
|
37
|
+
client.cluster.state
|
38
|
+
|
39
|
+
# the list of all index templates
|
40
|
+
client.cluster.templates
|
41
|
+
```
|
42
|
+
|
43
|
+
#### Index
|
44
|
+
|
45
|
+
The methods in the [Index](lib/elastomer/client/index.rb) class deal with the
|
46
|
+
management of indexes in the cluster. This includes setting up type mappings
|
47
|
+
and adjusting settings. The actual indexing and search of documents are
|
48
|
+
handled by the Docs class (discussed next).
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
require 'elastomer/client'
|
52
|
+
client = Elastomer::Client.new
|
53
|
+
|
54
|
+
index = client.index('twitter')
|
55
|
+
index.create(
|
56
|
+
:settings => { 'index.number_of_shards' => 3 },
|
57
|
+
:mappings => {
|
58
|
+
:tweet => {
|
59
|
+
:_source => { :enabled => true },
|
60
|
+
:_all => { :enabled => false },
|
61
|
+
:properties => {
|
62
|
+
:author => { :type => 'string', :index => 'not_analyzed' },
|
63
|
+
:tweet => { :type => 'string', :analyze => 'standard' }
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
)
|
68
|
+
|
69
|
+
index.exists?
|
70
|
+
|
71
|
+
index.exists? :type => 'tweet'
|
72
|
+
|
73
|
+
index.delete
|
74
|
+
```
|
75
|
+
|
76
|
+
#### Docs
|
77
|
+
|
78
|
+
This decomposition is the most questionable, but it's a starting point. The
|
79
|
+
[Docs](lib/elastomer/client/docs.rb) class handles the indexing and searching
|
80
|
+
of documents. Each instance is scoped to an index and optionally a document
|
81
|
+
type.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
require 'elastomer/client'
|
85
|
+
client = Elastomer::Client.new
|
86
|
+
|
87
|
+
docs = client.docs('twitter')
|
88
|
+
|
89
|
+
docs.index({
|
90
|
+
:_id => 1,
|
91
|
+
:_type => 'tweet',
|
92
|
+
:author => '@pea53',
|
93
|
+
:tweet => 'announcing Elastomer, the stupid simple ElasticSearch client'
|
94
|
+
})
|
95
|
+
|
96
|
+
docs.search({:query => {:match_all => {}}}, :search_type => 'count')
|
97
|
+
```
|
98
|
+
|
99
|
+
#### Performance
|
100
|
+
|
101
|
+
By default Elastomer uses Net::HTTP (via Faraday) to communicate with
|
102
|
+
ElasticSearch. You may find that Excon performs better for your use. To enable
|
103
|
+
Excon, add it to your bundle and then change your Elastomer initialization
|
104
|
+
thusly:
|
105
|
+
|
106
|
+
```
|
107
|
+
Elastomer::Client.new(url: YOUR_ES_URL, adapter: :excon)
|
108
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Notifications Support
|
2
|
+
|
3
|
+
Requiring `elastomer/notifications` enables support for broadcasting
|
4
|
+
elastomer events through ActiveSupport::Notifications.
|
5
|
+
|
6
|
+
The event namespace is `request.client.elastomer`.
|
7
|
+
|
8
|
+
## Sample event payload
|
9
|
+
|
10
|
+
```
|
11
|
+
:index => "index-test",
|
12
|
+
:type => nil,
|
13
|
+
:action => "docs.search",
|
14
|
+
:context=> nil,
|
15
|
+
:body => "{\"query\":{\"match_all\":{}}}",
|
16
|
+
:url => #<URI::HTTP:0x007fb6f3e98b60 URL:http://localhost:19200/index-test/_search?search_type=count>,
|
17
|
+
:method => :get,
|
18
|
+
:status => 200}
|
19
|
+
```
|
20
|
+
|
21
|
+
## Valid actions
|
22
|
+
- bulk
|
23
|
+
- cluster.get_settings
|
24
|
+
- cluster.health
|
25
|
+
- cluster.reroute
|
26
|
+
- cluster.state
|
27
|
+
- cluster.update_settings
|
28
|
+
- cluster.available
|
29
|
+
- cluster.get_aliases
|
30
|
+
- cluster.info
|
31
|
+
- cluster.shutdown
|
32
|
+
- cluster.update_aliases
|
33
|
+
- docs.delete
|
34
|
+
- docs.delete_by_query
|
35
|
+
- docs.explain
|
36
|
+
- docs.get
|
37
|
+
- docs.index
|
38
|
+
- docs.more_like_this
|
39
|
+
- docs.multi_get
|
40
|
+
- docs.search
|
41
|
+
- docs.source
|
42
|
+
- docs.update
|
43
|
+
- docs.validate
|
44
|
+
- index.analyze
|
45
|
+
- index.clear_cache
|
46
|
+
- index.close
|
47
|
+
- index.create
|
48
|
+
- index.delete
|
49
|
+
- index.delete_mapping
|
50
|
+
- index.exists
|
51
|
+
- index.flush
|
52
|
+
- index.get_aliases
|
53
|
+
- index.get_settings
|
54
|
+
- index.mapping
|
55
|
+
- index.open
|
56
|
+
- index.optimize
|
57
|
+
- index.refresh
|
58
|
+
- index.segments
|
59
|
+
- index.snapshot
|
60
|
+
- index.stats
|
61
|
+
- index.status
|
62
|
+
- index.update_mapping
|
63
|
+
- index.update_settings
|
64
|
+
- nodes.hot_threads
|
65
|
+
- nodes.info
|
66
|
+
- nodes.shutdown
|
67
|
+
- nodes.stats
|
68
|
+
- search.scan
|
69
|
+
- search.scroll
|
70
|
+
- template.create
|
71
|
+
- template.get
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'elastomer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "elastomer-client"
|
8
|
+
spec.version = Elastomer::VERSION
|
9
|
+
spec.authors = ["Tim Pease", "Grant Rodgers"]
|
10
|
+
spec.email = ["tim.pease@github.com", "grant.rodgers@github.com"]
|
11
|
+
spec.summary = %q{A library for interacting with the GitHub Search infrastructure}
|
12
|
+
spec.description = %q{Elastomer is a low level API client for the
|
13
|
+
Elasticsearch HTTP interface.}
|
14
|
+
spec.homepage = "https://github.com/github/elastomer-client"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "addressable", "~> 2.3"
|
23
|
+
spec.add_dependency "faraday", "~> 0.8"
|
24
|
+
spec.add_dependency "multi_json", "~> 1.7"
|
25
|
+
spec.add_dependency "semantic", "~> 1.3"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
28
|
+
spec.add_development_dependency "minitest","~> 4.7"
|
29
|
+
spec.add_development_dependency "rake"
|
30
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
require 'addressable/template'
|
2
|
+
require 'faraday'
|
3
|
+
require 'multi_json'
|
4
|
+
require 'semantic'
|
5
|
+
|
6
|
+
require 'elastomer/version'
|
7
|
+
|
8
|
+
module Elastomer
|
9
|
+
|
10
|
+
class Client
|
11
|
+
|
12
|
+
# Create a new client that can be used to make HTTP requests to the
|
13
|
+
# ElasticSearch server.
|
14
|
+
#
|
15
|
+
# opts - The options Hash
|
16
|
+
# :host - the host as a String
|
17
|
+
# :port - the port number of the server
|
18
|
+
# :url - the URL as a String (overrides :host and :port)
|
19
|
+
# :read_timeout - the timeout in seconds when reading from an HTTP connection
|
20
|
+
# :open_timeout - the timeout in seconds when opening an HTTP connection
|
21
|
+
# :adapter - the Faraday adapter to use (defaults to :excon)
|
22
|
+
# :opaque_id - set to `true` to use the 'X-Opaque-Id' request header
|
23
|
+
#
|
24
|
+
def initialize( opts = {} )
|
25
|
+
host = opts.fetch :host, 'localhost'
|
26
|
+
port = opts.fetch :port, 9200
|
27
|
+
@url = opts.fetch :url, "http://#{host}:#{port}"
|
28
|
+
|
29
|
+
uri = Addressable::URI.parse @url
|
30
|
+
@host = uri.host
|
31
|
+
@port = uri.port
|
32
|
+
|
33
|
+
@read_timeout = opts.fetch :read_timeout, 5
|
34
|
+
@open_timeout = opts.fetch :open_timeout, 2
|
35
|
+
@adapter = opts.fetch :adapter, Faraday.default_adapter
|
36
|
+
@opaque_id = opts.fetch :opaque_id, false
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :host, :port, :url
|
40
|
+
attr_reader :read_timeout, :open_timeout
|
41
|
+
|
42
|
+
# Returns true if the server is available; returns false otherwise.
|
43
|
+
def available?
|
44
|
+
response = head '/', :action => 'cluster.available'
|
45
|
+
response.success?
|
46
|
+
rescue StandardError
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the version String of the attached ElasticSearch instance.
|
51
|
+
def version
|
52
|
+
@version ||= info['version']['number']
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a Semantic::Version for the attached ElasticSearch instance.
|
56
|
+
# See https://rubygems.org/gems/semantic
|
57
|
+
def semantic_version
|
58
|
+
Semantic::Version.new(version)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the information Hash from the attached ElasticSearch instance.
|
62
|
+
def info
|
63
|
+
response = get '/', :action => 'cluster.info'
|
64
|
+
response.body
|
65
|
+
end
|
66
|
+
|
67
|
+
# Internal: Provides access to the Faraday::Connection used by this client
|
68
|
+
# for all requests to the server.
|
69
|
+
#
|
70
|
+
# Returns a Faraday::Connection
|
71
|
+
def connection
|
72
|
+
@connection ||= Faraday.new(url) do |conn|
|
73
|
+
conn.request :encode_json
|
74
|
+
conn.response :parse_json
|
75
|
+
conn.request :opaque_id if @opaque_id
|
76
|
+
|
77
|
+
Array === @adapter ?
|
78
|
+
conn.adapter(*@adapter) :
|
79
|
+
conn.adapter(@adapter)
|
80
|
+
|
81
|
+
conn.options[:timeout] = read_timeout
|
82
|
+
conn.options[:open_timeout] = open_timeout
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Internal: Sends an HTTP HEAD request to the server.
|
87
|
+
#
|
88
|
+
# path - The path as a String
|
89
|
+
# params - Parameters Hash
|
90
|
+
#
|
91
|
+
# Returns a Faraday::Response
|
92
|
+
def head( path, params = {} )
|
93
|
+
request :head, path, params
|
94
|
+
end
|
95
|
+
|
96
|
+
# Internal: Sends an HTTP GET request to the server.
|
97
|
+
#
|
98
|
+
# path - The path as a String
|
99
|
+
# params - Parameters Hash
|
100
|
+
#
|
101
|
+
# Returns a Faraday::Response
|
102
|
+
# Raises an Elastomer::Client::Error on 4XX and 5XX responses
|
103
|
+
def get( path, params = {} )
|
104
|
+
request :get, path, params
|
105
|
+
end
|
106
|
+
|
107
|
+
# Internal: Sends an HTTP PUT request to the server.
|
108
|
+
#
|
109
|
+
# path - The path as a String
|
110
|
+
# params - Parameters Hash
|
111
|
+
#
|
112
|
+
# Returns a Faraday::Response
|
113
|
+
# Raises an Elastomer::Client::Error on 4XX and 5XX responses
|
114
|
+
def put( path, params = {} )
|
115
|
+
request :put, path, params
|
116
|
+
end
|
117
|
+
|
118
|
+
# Internal: Sends an HTTP POST request to the server.
|
119
|
+
#
|
120
|
+
# path - The path as a String
|
121
|
+
# params - Parameters Hash
|
122
|
+
#
|
123
|
+
# Returns a Faraday::Response
|
124
|
+
# Raises an Elastomer::Client::Error on 4XX and 5XX responses
|
125
|
+
def post( path, params = {} )
|
126
|
+
request :post, path, params
|
127
|
+
end
|
128
|
+
|
129
|
+
# Internal: Sends an HTTP DELETE request to the server.
|
130
|
+
#
|
131
|
+
# path - The path as a String
|
132
|
+
# params - Parameters Hash
|
133
|
+
#
|
134
|
+
# Returns a Faraday::Response
|
135
|
+
# Raises an Elastomer::Client::Error on 4XX and 5XX responses
|
136
|
+
def delete( path, params = {} )
|
137
|
+
request :delete, path, params
|
138
|
+
end
|
139
|
+
|
140
|
+
# Internal: Sends an HTTP request to the server. If the `params` Hash
|
141
|
+
# contains a :body key, it will be deleted from the Hash and the value
|
142
|
+
# will be used as the body of the request.
|
143
|
+
#
|
144
|
+
# method - The HTTP method to send [:head, :get, :put, :post, :delete]
|
145
|
+
# path - The path as a String
|
146
|
+
# params - Parameters Hash
|
147
|
+
# :body - Will be used as the request body
|
148
|
+
# :read_timeout - Optional read timeout (in seconds) for the request
|
149
|
+
#
|
150
|
+
# Returns a Faraday::Response
|
151
|
+
# Raises an Elastomer::Client::Error on 4XX and 5XX responses
|
152
|
+
def request( method, path, params )
|
153
|
+
body = params.delete :body
|
154
|
+
body = MultiJson.dump body if Hash === body
|
155
|
+
|
156
|
+
read_timeout = params.delete :read_timeout
|
157
|
+
|
158
|
+
path = expand_path path, params
|
159
|
+
|
160
|
+
response = instrument(path, body, params) do
|
161
|
+
case method
|
162
|
+
when :head
|
163
|
+
connection.head(path) { |req| req.options[:timeout] = read_timeout if read_timeout }
|
164
|
+
|
165
|
+
when :get
|
166
|
+
connection.get(path) { |req|
|
167
|
+
req.body = body if body
|
168
|
+
req.options[:timeout] = read_timeout if read_timeout
|
169
|
+
}
|
170
|
+
|
171
|
+
when :put
|
172
|
+
connection.put(path, body) { |req| req.options[:timeout] = read_timeout if read_timeout }
|
173
|
+
|
174
|
+
when :post
|
175
|
+
connection.post(path, body) { |req| req.options[:timeout] = read_timeout if read_timeout }
|
176
|
+
|
177
|
+
when :delete
|
178
|
+
connection.delete(path) { |req|
|
179
|
+
req.body = body if body
|
180
|
+
req.options[:timeout] = read_timeout if read_timeout
|
181
|
+
}
|
182
|
+
|
183
|
+
else
|
184
|
+
raise ArgumentError, "unknown HTTP request method: #{method.inspect}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
handle_errors response
|
189
|
+
|
190
|
+
rescue Faraday::Error::TimeoutError => boom
|
191
|
+
raise ::Elastomer::Client::TimeoutError.new(boom, path)
|
192
|
+
|
193
|
+
# ensure
|
194
|
+
# # FIXME: this is here until we get a real logger in place
|
195
|
+
# STDERR.puts "[#{response.status.inspect}] curl -X#{method.to_s.upcase} '#{url}#{path}'" unless response.nil?
|
196
|
+
end
|
197
|
+
|
198
|
+
# Internal: Apply path expansions to the `path` and append query
|
199
|
+
# parameters to the `path`. We are using an Addressable::Template to
|
200
|
+
# replace '{expansion}' fields found in the path with the values extracted
|
201
|
+
# from the `params` Hash. Any remaining elements in the `params` hash are
|
202
|
+
# treated as query parameters and appended to the end of the path.
|
203
|
+
#
|
204
|
+
# path - The path as a String
|
205
|
+
# params - Parameters Hash
|
206
|
+
#
|
207
|
+
# Examples
|
208
|
+
#
|
209
|
+
# expand_path('/foo{/bar}', {:bar => 'hello', :q => 'what', :p => 2})
|
210
|
+
# #=> '/foo/hello?q=what&p=2'
|
211
|
+
#
|
212
|
+
# expand_path('/foo{/bar}{/baz}', {:baz => 'no bar'}
|
213
|
+
# #=> '/foo/no%20bar'
|
214
|
+
#
|
215
|
+
# Returns an Addressable::Uri
|
216
|
+
def expand_path( path, params )
|
217
|
+
template = Addressable::Template.new path
|
218
|
+
|
219
|
+
expansions = {}
|
220
|
+
query_values = params.dup
|
221
|
+
query_values.delete :action
|
222
|
+
query_values.delete :context
|
223
|
+
|
224
|
+
template.keys.map(&:to_sym).each do |key|
|
225
|
+
value = query_values.delete key
|
226
|
+
value = assert_param_presence(value, key) unless path =~ /{\/#{key}}/ && value.nil?
|
227
|
+
expansions[key] = value
|
228
|
+
end
|
229
|
+
|
230
|
+
uri = template.expand(expansions)
|
231
|
+
uri.query_values = query_values unless query_values.empty?
|
232
|
+
uri.to_s
|
233
|
+
end
|
234
|
+
|
235
|
+
# Internal: A noop method that simply yields to the block. This method
|
236
|
+
# will be replaced when the 'elastomer/notifications' module is included.
|
237
|
+
#
|
238
|
+
# path - The full request path as a String
|
239
|
+
# body - The request body as a String or `nil`
|
240
|
+
# params - The request params Hash
|
241
|
+
# block - The block that will be instrumented
|
242
|
+
#
|
243
|
+
# Returns the response from the block
|
244
|
+
def instrument( path, body, params )
|
245
|
+
yield
|
246
|
+
end
|
247
|
+
|
248
|
+
# Internal: Inspect the Faraday::Response and raise an error if the status
|
249
|
+
# is in the 5XX range or if the response body contains an 'error' field.
|
250
|
+
# In the latter case, the value of the 'error' field becomes our exception
|
251
|
+
# message. In the absence of an 'error' field the response body is used
|
252
|
+
# as the exception message.
|
253
|
+
#
|
254
|
+
# The raised exception will contain the response object.
|
255
|
+
#
|
256
|
+
# response - The Faraday::Response object.
|
257
|
+
#
|
258
|
+
# Returns the response.
|
259
|
+
# Raises an Elastomer::Client::Error on 500 responses or responses
|
260
|
+
# containing and 'error' field.
|
261
|
+
def handle_errors( response )
|
262
|
+
raise Error, response if response.status >= 500
|
263
|
+
raise Error, response if Hash === response.body && response.body['error']
|
264
|
+
|
265
|
+
response
|
266
|
+
end
|
267
|
+
|
268
|
+
# Internal: Ensure that the parameter has a valid value. Things like `nil`
|
269
|
+
# and empty strings are right out. This method also performs a little
|
270
|
+
# formating on the parameter:
|
271
|
+
#
|
272
|
+
# * leading and trailing whitespace is removed
|
273
|
+
# * arrays are flattend
|
274
|
+
# * and then joined into a String
|
275
|
+
# * numerics are converted to their string equivalents
|
276
|
+
#
|
277
|
+
# param - The param Object to validate
|
278
|
+
# name - Optional param name as a String (used in exception messages)
|
279
|
+
#
|
280
|
+
# Returns the validated param as a String.
|
281
|
+
# Raises an ArgumentError if the param is not valid.
|
282
|
+
def assert_param_presence( param, name = 'input value' )
|
283
|
+
case param
|
284
|
+
when String, Numeric
|
285
|
+
param = param.to_s.strip
|
286
|
+
raise ArgumentError, "#{name} cannot be blank: #{param.inspect}" if param =~ /\A\s*\z/
|
287
|
+
param
|
288
|
+
|
289
|
+
when Array
|
290
|
+
param.flatten.map { |item| assert_param_presence(item, name) }.join(',')
|
291
|
+
|
292
|
+
when nil
|
293
|
+
raise ArgumentError, "#{name} cannot be nil"
|
294
|
+
|
295
|
+
else
|
296
|
+
raise ArgumentError, "#{name} is invalid: #{param.inspect}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
end # Client
|
301
|
+
end # Elastomer
|
302
|
+
|
303
|
+
# require all files in the `client` sub-directory
|
304
|
+
Dir.glob(File.expand_path('../client/*.rb', __FILE__)).each { |fn| require fn }
|
305
|
+
|
306
|
+
# require all files in the `middleware` sub-directory
|
307
|
+
Dir.glob(File.expand_path('../middleware/*.rb', __FILE__)).each { |fn| require fn }
|