grac 4.2.0 → 4.3.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 (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/grac/client.rb +128 -120
  3. data/lib/grac/version.rb +1 -1
  4. metadata +10 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc29285a1603b9ee7559be0edc7949a950611dd04aac4399400816381def0a72
4
- data.tar.gz: b39fa9ab54323755a4fb4f80b932f26f21b0232d12b9b790c308b7960e03239f
3
+ metadata.gz: 8460d522d154cc9553f4a67210e6509fd1e1e5d9b956cccf4e3e4701eccd6135
4
+ data.tar.gz: a8c70bc97eadf86324176c301b4bc5bd856f4d20e402ba111236860734d8e0b6
5
5
  SHA512:
6
- metadata.gz: d8764509b72746e4b692f0cad8f06f8ecf23b41f0a0e8dc8635507da57f2567af6e7ac008207242398029a26298ce401d10fde3fa2c3c80d621c09a3ffb9013e
7
- data.tar.gz: 112f99d47388faff6743122ebd3044d5466cd46ca7857363cb4da6740bb16445b3de545ca48f3ba8c5de1247718f09900a32766139d5bcc5fac93ec448bb9479
6
+ metadata.gz: 2db352fde7d23f9e42d578b50c08c702232c096d0de59d4ed1075bf4db63946fbff16335de25bdbac7d076e9f176a8b18b78f506bd7c5b3fe8bb81e4d9d8ba57
7
+ data.tar.gz: 146317de75aa8ab0a276016b90cd3aac920594424edc2f601b650bc64746ff531c360b34997be1e6d1af73676826948ddb573ac4be2b11e004a24aed4250d779
data/lib/grac/client.rb CHANGED
@@ -8,6 +8,7 @@ require_relative './response'
8
8
 
9
9
  module Grac
10
10
  class Client
11
+
11
12
  attr_reader :uri
12
13
 
13
14
  def initialize(uri, options = {})
@@ -15,21 +16,22 @@ module Grac
15
16
 
16
17
  @uri = uri
17
18
  @options = {
18
- :connecttimeout => options[:connecttimeout] || 0.1,
19
- :timeout => options[:timeout] || 15,
20
- :params => options[:params] || {},
21
- :headers => {
22
- "User-Agent" => "Grac v#{Grac::VERSION}",
23
- "Content-Type" => "application/json;charset=utf-8"
19
+ connecttimeout: options[:connecttimeout] || 0.1,
20
+ timeout: options[:timeout] || 15,
21
+ params: options[:params] || {},
22
+ headers: {
23
+ 'User-Agent' => "Grac v#{Grac::VERSION}",
24
+ 'Content-Type' => 'application/json;charset=utf-8'
24
25
  }.merge(options[:headers] || {}),
25
- :postprocessing => {},
26
- :middleware => options[:middleware] || []
26
+ postprocessing: {},
27
+ middleware: options[:middleware] || [],
28
+ retry_get_head: options.fetch(:retry_get_head, true),
27
29
  }
28
30
 
29
31
  if options[:postprocessing]
30
32
  options[:postprocessing]
31
33
  .each_with_object(postprocessing = {}) do |(pattern, transformation), obj|
32
- if pattern.kind_of?(Regexp)
34
+ if pattern.is_a?(Regexp)
33
35
  obj[pattern] = transformation
34
36
  else
35
37
  obj[Regexp.new(pattern)] = transformation
@@ -49,10 +51,12 @@ module Grac
49
51
  end
50
52
 
51
53
  def set(options = {})
52
- options = options.merge({
53
- headers: @options[:headers].merge(options[:headers] || {}),
54
- middleware: @options[:middleware] + (options[:middleware] || [])
55
- })
54
+ options = options.merge(
55
+ {
56
+ headers: @options[:headers].merge(options[:headers] || {}),
57
+ middleware: @options[:middleware] + (options[:middleware] || []),
58
+ },
59
+ )
56
60
 
57
61
  self.class.new(@uri, @options.merge(options))
58
62
  end
@@ -64,35 +68,38 @@ module Grac
64
68
  self.class.new("#{@uri}#{path}", @options)
65
69
  end
66
70
 
67
- %w{post put patch}.each do |method|
71
+ ['post', 'put', 'patch'].each do |method|
68
72
  define_method method do |body = {}, params = {}|
69
- response = build_and_run(method, { :body => body, :params => params })
73
+ response = build_and_run(method, { body: body, params: params })
70
74
  check_response(method, response)
71
75
  end
72
76
  end
73
77
 
74
- %w{get delete}.each do |method|
78
+ ['get', 'delete'].each do |method|
75
79
  define_method method do |params = {}|
76
- response = build_and_run(method, { :params => params })
80
+ response = build_and_run(method, { params: params })
77
81
  check_response(method, response)
78
82
  end
79
83
  end
80
84
 
81
85
  def call(opts, request_uri, method, params, body)
82
86
  request_hash = {
83
- :method => method,
84
- :params => params, # Query params are escaped by Typhoeus
85
- :body => body,
86
- :connecttimeout => opts[:connecttimeout],
87
- :timeout => opts[:timeout],
88
- :headers => opts[:headers]
87
+ method: method,
88
+ params: params, # Query params are escaped by Typhoeus
89
+ body: body,
90
+ connecttimeout: opts[:connecttimeout],
91
+ timeout: opts[:timeout],
92
+ headers: opts[:headers],
89
93
  }
90
94
 
91
95
  request = ::Typhoeus::Request.new(request_uri, request_hash)
92
96
  response = request.run
93
97
 
94
98
  # Retry GET and HEAD requests - modifying requests might not be idempotent
95
- response = request.run if response.timed_out? && ['get', 'head'].include?(method)
99
+ # Only retry those requests if the feature is enabled
100
+ if response.timed_out? && ['get', 'head'].include?(method) && @options[:retry_get_head]
101
+ response = request.run
102
+ end
96
103
 
97
104
  # A request can time out while receiving data. In this case response.code might indicate
98
105
  # success although data hasn't been fully transferred. Thus rely on Typhoeus for
@@ -119,122 +126,123 @@ module Grac
119
126
 
120
127
  private
121
128
 
122
- def build_and_run(method, options = {})
123
- body = prepare_body_by_content_type(options[:body])
124
- params = @options[:params].merge(options[:params] || {})
125
- return middleware_chain.call(@options, uri, method, params, body)
126
- end
127
-
128
- def headers
129
- @options[:headers] || {}
130
- end
131
-
132
- def prepare_body_by_content_type(body)
133
- return nil if body.nil? || body.empty?
134
-
135
- case headers['Content-Type']
136
- when /\Aapplication\/json/
137
- return body.to_json
138
- when /\Aapplication\/x-www-form-urlencoded/
139
- # Typhoeus will take care of the encoding when receiving a hash
140
- return body
141
- else
142
- # Do not encode other unknown Content-Types either.
143
- # The default is JSON through the Content-Type header which is set by default.
144
- return body
129
+ def build_and_run(method, options = {})
130
+ body = prepare_body_by_content_type(options[:body])
131
+ params = @options[:params].merge(options[:params] || {})
132
+ return middleware_chain.call(@options, uri, method, params, body)
145
133
  end
146
- end
147
134
 
148
- def middleware_chain
149
- callee = self
135
+ def headers
136
+ @options[:headers] || {}
137
+ end
150
138
 
151
- @options[:middleware].reverse.each do |mw|
152
- if mw.kind_of?(Array)
153
- middleware_class = mw[0]
154
- params = mw[1..-1]
139
+ def prepare_body_by_content_type(body)
140
+ return nil if body.nil? || body.empty?
155
141
 
156
- callee = middleware_class.new(callee, *params)
142
+ case headers['Content-Type']
143
+ when /\Aapplication\/json/
144
+ return body.to_json
145
+ when /\Aapplication\/x-www-form-urlencoded/
146
+ # Typhoeus will take care of the encoding when receiving a hash
147
+ return body
157
148
  else
158
- callee = mw.new(callee)
149
+ # Do not encode other unknown Content-Types either.
150
+ # The default is JSON through the Content-Type header which is set by default.
151
+ return body
159
152
  end
160
153
  end
161
154
 
162
- return callee
163
- end
155
+ def middleware_chain
156
+ callee = self
164
157
 
165
- def check_response(method, response)
166
- case response.code
167
- when 200..203, 206..299
168
- # unknown status codes must be treated as the x00 of their class, so 200
169
- if response.json_content?
170
- return postprocessing(response.parsed_json)
171
- end
158
+ @options[:middleware].reverse.each do |mw|
159
+ if mw.kind_of?(Array)
160
+ middleware_class = mw[0]
161
+ params = mw[1..-1]
172
162
 
173
- return response.body
174
- when 204, 205
175
- return true
176
- when 0
177
- raise Exception::RequestFailed.new(method, response.effective_url, response.return_message)
178
- else
179
- begin
180
- # The Response class doesn't have enough information to create a proper exception, so
181
- # catch its exception and raise a proper one.
182
- parsed_body = response.parsed_json
183
- rescue Exception::InvalidContent
184
- raise Exception::ErrorWithInvalidContent.new(
185
- method,
186
- response.effective_url,
187
- response.code,
188
- response.body,
189
- 'json'
190
- )
191
- end
192
- case response.code
193
- when 400
194
- raise Exception::BadRequest.new(method, response.effective_url, parsed_body)
195
- when 403
196
- raise Exception::Forbidden.new(method, response.effective_url, parsed_body)
197
- when 404
198
- raise Exception::NotFound.new(method, response.effective_url, parsed_body)
199
- when 409
200
- raise Exception::Conflict.new(method, response.effective_url, parsed_body)
163
+ callee = middleware_class.new(callee, *params)
201
164
  else
202
- raise Exception::ServiceError.new(method, response.effective_url, parsed_body)
165
+ callee = mw.new(callee)
203
166
  end
167
+ end
168
+
169
+ return callee
204
170
  end
205
- end
206
171
 
207
- def postprocessing(data, processing = nil)
208
- return data if @options[:postprocessing].nil? || @options[:postprocessing].empty?
172
+ def check_response(method, response)
173
+ case response.code
174
+ when 200..203, 206..299
175
+ # unknown status codes must be treated as the x00 of their class, so 200
176
+ if response.json_content?
177
+ return postprocessing(response.parsed_json)
178
+ end
179
+
180
+ return response.body
181
+ when 204, 205
182
+ return true
183
+ when 0
184
+ raise Exception::RequestFailed.new(method, response.effective_url, response.return_message)
185
+ else
186
+ begin
187
+ # The Response class doesn't have enough information to create a proper exception, so
188
+ # catch its exception and raise a proper one.
189
+ parsed_body = response.parsed_json
190
+ rescue Exception::InvalidContent
191
+ raise Exception::ErrorWithInvalidContent.new(
192
+ method,
193
+ response.effective_url,
194
+ response.code,
195
+ response.body,
196
+ 'json'
197
+ )
198
+ end
199
+ case response.code
200
+ when 400
201
+ raise Exception::BadRequest.new(method, response.effective_url, parsed_body)
202
+ when 403
203
+ raise Exception::Forbidden.new(method, response.effective_url, parsed_body)
204
+ when 404
205
+ raise Exception::NotFound.new(method, response.effective_url, parsed_body)
206
+ when 409
207
+ raise Exception::Conflict.new(method, response.effective_url, parsed_body)
208
+ else
209
+ raise Exception::ServiceError.new(method, response.effective_url, parsed_body)
210
+ end
211
+ end
212
+ end
213
+
214
+ def postprocessing(data, processing = nil)
215
+ return data if @options[:postprocessing].nil? || @options[:postprocessing].empty?
209
216
 
210
- if data.kind_of?(Hash)
211
- data.each do |key, value|
212
- processing = nil
213
- regexp = @options[:postprocessing].keys.detect { |pattern| pattern.match?(key) }
217
+ if data.kind_of?(Hash)
218
+ data.each do |key, value|
219
+ processing = nil
220
+ regexp = @options[:postprocessing].keys.detect { |pattern| pattern.match?(key) }
214
221
 
215
- if !regexp.nil?
216
- processing = @options[:postprocessing][regexp]
222
+ if !regexp.nil?
223
+ processing = @options[:postprocessing][regexp]
224
+ end
225
+ data[key] = postprocessing(value, processing)
217
226
  end
218
- data[key] = postprocessing(value, processing)
219
- end
220
- elsif data.kind_of?(Array)
221
- data.each_with_index do |value, index|
222
- data[index] = postprocessing(value, processing)
227
+ elsif data.kind_of?(Array)
228
+ data.each_with_index do |value, index|
229
+ data[index] = postprocessing(value, processing)
230
+ end
231
+ else
232
+ data = processing.nil? ? data : processing.call(data)
223
233
  end
224
- else
225
- data = processing.nil? ? data : processing.call(data)
234
+
235
+ return data
226
236
  end
227
237
 
228
- return data
229
- end
238
+ def escape_url_param(value)
239
+ # We don't want spaces to be encoded as plus sign - a plus sign can be ambiguous in a URL and
240
+ # either represent a plus sign or a space.
241
+ # CGI::escape replaces all plus signs with their percent-encoding representation, so all
242
+ # remaining plus signs are spaces. Replacing these with a space's percent encoding makes the
243
+ # encoding unambiguous.
244
+ CGI::escape(value).gsub('+', '%20')
245
+ end
230
246
 
231
- def escape_url_param(value)
232
- # We don't want spaces to be encoded as plus sign - a plus sign can be ambiguous in a URL and
233
- # either represent a plus sign or a space.
234
- # CGI::escape replaces all plus signs with their percent-encoding representation, so all
235
- # remaining plus signs are spaces. Replacing these with a space's percent encoding makes the
236
- # encoding unambiguous.
237
- CGI::escape(value).gsub('+', '%20')
238
- end
239
247
  end
240
248
  end
data/lib/grac/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Grac
2
- VERSION = "4.2.0"
2
+ VERSION = "4.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grac
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.0
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Schoknecht
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-15 00:00:00.000000000 Z
11
+ date: 2024-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -72,28 +72,28 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 3.0.1
75
+ version: '3.1'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 3.0.1
82
+ version: '3.1'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rack-test
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 2.0.2
89
+ version: '2.1'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 2.0.2
96
+ version: '2.1'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: oj
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -138,7 +138,7 @@ homepage: https://github.com/Barzahlen/grac
138
138
  licenses:
139
139
  - MIT
140
140
  metadata: {}
141
- post_install_message:
141
+ post_install_message:
142
142
  rdoc_options: []
143
143
  require_paths:
144
144
  - lib
@@ -153,8 +153,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
153
  - !ruby/object:Gem::Version
154
154
  version: '0'
155
155
  requirements: []
156
- rubygems_version: 3.1.6
157
- signing_key:
156
+ rubygems_version: 3.5.3
157
+ signing_key:
158
158
  specification_version: 4
159
159
  summary: Very generic client for REST API with basic error handling
160
160
  test_files: []